EXTRACLANGTOOLS(1)
๐ธ NAME ๐ธ
extraclangtools โ Extra Clang Tools Documentation ๐
Okay bestie, welcome to the clang-tools-extra project, which is literally the home of all the extra tools built using Clang's tooling APIs. This page? It's the manual page EXTRACLANGTOOLS(1), section 1. Iykyk. โจ
๐ EXTRA CLANG TOOLS 20.1.8 RELEASE NOTES ๐
Here's the whole table of contents, no skips, no edits:
- Introduction
- What's New in Extra Clang Tools 20.1.8?
- Major New Features
- Improvements to clangd
- Inlay hints
- Diagnostics
- Semantic Highlighting
- Compile flags
- Hover
- Code completion
- Code actions
- Signature help
- Cross-references
- Objective-C
- Clang-tidy integration
- C++20 Modules Support
- Miscellaneous
- Improvements to clang-doc
- Improvements to clang-query
- 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 ๐
๐ Introduction ๐
So this document is the release notes for the Extra Clang Tools, which is part of the Clang release 20.1.8. We're gonna describe the status of the Extra Clang Tools in some detail โ like, the major improvements since the last release plus all the new feature work. Every single LLVM release is downloadable from the LLVM releases web site, go get 'em. ๐ฆ
Want more tea about Clang or LLVM, including the info about the latest release? Check the Clang Web Site or the LLVM Web Site. Simple as that.
โ ๏ธ Real talk though: if you're reading this file from a Git checkout or the main Clang web page, then this document applies to the next release, not the current one. Not me casually reading spoilers ๐. To see the release notes for a specific release, hit up the releases page.
โญ What's New in Extra Clang Tools 20.1.8? โญ
Some of the major new features and improvements to Extra Clang Tools are listed right here. Generic improvements to Extra Clang Tools as a whole, or to its underlying infrastructure, get described first, and then we get into the tool-specific sections. Order matters, bestie. ๐
๐ Major New Features ๐
- The
clang-pseudotool is incomplete and does not have active maintainers, so it has been removed. See the RFC for more details. RIP boo, you had potential ๐ญ
...
๐ง Improvements to clangd ๐ง
โจ Inlay hints โจ
- Added support for inlay hints for default arguments, enabled using the
DefaultArgumentsconfig option (#GH95712) ๐
โจ Diagnostics โจ
(nothing new listed here in this section โ it's giving peaceful, quiet, unbothered โจ)
โจ Semantic Highlighting โจ
(same deal, no items listed under this one โ just vibes ๐ธ)
โจ Compile flags โจ
- Fixed a bug where
clangdwould unnecessarily reparse open files whose compile command did not change when receiving a new compile command via an LSPworkspace/configurationrequest (#GH115438) โ no more wasting our girl's energy fr fr
โจ Hover โจ
- Hovering over a function name now shows the function's documentation comment even if the comment is written above the function's out-of-line definition in a different source file (#GH67802) โ the way it finally found the comment across files ๐ญ๐
โจ Code completion โจ
- Added an
ArgumentListsconfig option underCompletion. This is a more flexible version of the--function-arg-placeholderscommand line flag, allowing users more detailed control of what is inserted in argument list position when clangd completes the name of a function in a function call context. (#GH111322) - Clangd now supports configuring which headers should be inserted using
<>vs.""syntax using theQuotedHeadersandAngledHeadersconfig options underStyle(#GH67749) โ brackets but make it fashion ๐ - Added completion for C++20 keywords. โจ
- Improved code completion behaviour in dependent/templated code. ๐ฅ
- Completion items now include documentation comments in more cases (#GH120099)
โจ Code actions โจ
- Added Swap operands tweak for certain binary operators.
- Improved the extract-to-function code action to allow extracting statements with overloaded operators like
<<ofstd::ostream. - Define outline now handles member functions of class templates, and member function templates.
- Extract variable can now operate on the top-level expression in an expression statement (#GH112525)
โจ Signature help โจ
(no items listed here โ the section is just standing there, gorgeous, saying nothing ๐ )
โจ Cross-references โจ
- Clangd now supports the "outgoing calls" direction of call hierarchy (#GH77556)
- Call hierarchy can now be invoked on fields and namespace-scope variables (#GH113900)
- Improved heuristics for filtering out generated Protobuf symbol names during indexing (#GH110091)
- Compiler intrinsics defined in
*intrin.hsystem headers are now indexed even if they have reserved names (#GH119735) ๐ - Various improvements to go-to-definition in templated code
โจ Objective-C โจ
(nothing listed here โ quiet luxury ๐ฆ)
โจ Clang-tidy integration โจ
- Improved robustness in handling clang-tidy check names (#GH109421)
โจ C++20 Modules Support โจ
- Support code completion for symbols defined in modules (#GH110083)
- Improve performance when opening files that import modules (#GH106683)
- Compile commands for modules now respect modifications specified in
.clangdfiles (#GH122606)
โจ Miscellaneous โจ
- Fixed an OOM affecting some versions of libcxx headers compiled in C++20 mode (#GH108866) โ an OOM? in this economy?? ๐ญ
- Various other stability improvements, e.g. crash fixes
๐ซถ Improvements to clang-doc ๐ซถ
(no items listed under this one in this release โ the section exists, it's just being mysterious ๐ธ)
๐ซถ Improvements to clang-query ๐ซถ
- Added
set enable-profile true/falsecommand for basic matcher profiling. โจ
๐ซถ Improvements to clang-tidy ๐ซถ
- Improved
clang-tidy-diff.pyscript. Add the-only-check-in-dboption to exclude files not present in the compilation database, avoiding false-negative results. ๐ฅ - Improved
run-clang-tidy.pyscript. Fixed minor shutdown noise happening on certain platforms when interrupting the script. - Improved
clang-tidy:- add support for
--verify-configflag to check the configuration options of the Clang Static Analyzer Checks. - accept parameters file in command line.
- fix incorrect configuration file path resolving when file paths contain
... - remove global options for most of checks. All options are changed to local options except
IncludeStyle,StrictModeandIgnoreMacros. Global scopedStrictModeandIgnoreMacrosare deprecated and will be removed in further releases.
- add support for
โ ๏ธ Heads up bestie: those two โ global scopedStrictModeandIgnoreMacrosโ are deprecated. They will be removed in further releases. Don't get attached. ๐
And here's the rundown of which check lost which option from the global option zone, in list form since I can't do the cute box thing:
bugprone-reserved-identifierโ options removed from global option:AggressiveDependentMemberLookupbugprone-unchecked-optional-accessโ options removed from global option:IgnoreSmartPointerDereferencecppcoreguidelines-pro-type-member-initโ options removed from global option:UseAssignmentcppcoreguidelines-rvalue-reference-param-not-movedโ options removed from global option:AllowPartialMove;IgnoreUnnamedParams;IgnoreNonDeducedTemplateTypesmisc-include-cleanerโ options removed from global option:IgnoreHeaders;DeduplicateFindingsperformance-inefficient-vector-operationโ options removed from global option:EnableProtoreadability-identifier-namingโ options removed from global option:AggressiveDependentMemberLookupreadability-inconsistent-declaration-parameter-nameโ options removed from global option:Strictreadability-redundant-access-specifiersโ options removed from global option:CheckFirstDeclarationreadability-redundant-castingโ options removed from global option:IgnoreTypeAliases
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- Fixed a bug in clang-tidy where
HeaderFilterRegexwas lowkey doing nothing when you passed it through the.clang-tidyfile. not anymore bestie ๐ - Fixed a bug in
run_clang_tidy.pywhere the program would not correctly show the checks that got enabled by the top-level.clang-tidyfile. it was giving fake news, we fixed it ๐ซถ
โจ New checks โจ
bugprone-bitwise-pointer-castโ brand new. It yells at code that tries to cast between pointers usingstd::bit_castormemcpy. like girl. no.bugprone-incorrect-enable-shared-from-thisโ catches classes or structs that do not publicly inherit fromstd::enable_shared_from_this, because callingshared_from_thison them will otherwise cause unintended behavior. so real.bugprone-nondeterministic-pointer-iteration-orderโ sniffs out nondeterministic usages of pointers in unordered containers. chaos detected ๐bugprone-tagged-union-member-countโ warns you about tagged unions where the number of tags is different from the number of data members inside the union. the math is not mathing and it will tell you.modernize-use-integer-sign-comparisonโ swaps comparisons between signed and unsigned integers for their safe C++20std::cmp_*alternative, if available. glow up ๐ฅportability-template-virtual-member-functionโ finds cases when an uninstantiated virtual member function inside a template class causes cross-compiler incompatibility. portability queen behavior ๐
โจ New check aliases โจ
- New alias
cert-arr39-cโbugprone-sizeof-expressionwas added. we love a rebrand ๐
โจ Changes in existing checks โจ
- Improved
altera-id-dependent-backward-branchby fixing crashes coming from invalid code. no more crashing out ๐ - Improved
bugprone-branch-cloneto better detect branch clones, because it now catches duplicate inner and outerifstatements. - Improved
bugprone-casting-through-voidso it suggests replacing the offending code withreinterpret_cast, to more clearly express intent. say what you mean bestie. - Improved
bugprone-dangling-handleto treatstd::spanas a handle class. - Improved
bugprone-exception-escapeby fixing false positives when aconstevalfunction hasthrowstatements. - Improved
bugprone-forwarding-reference-overloadby fixing a crash when determining if anenable_if[_t]was found. - Improved
bugprone-narrowing-conversionsto avoid incorrect check results when the floating point type is notfloat,doubleandlong double. - Improved
bugprone-optional-value-conversionto support detecting conversion directly bystd::make_uniqueandstd::make_shared. - Improved
bugprone-posix-returnto support integer literals as the LHS and a posix call as the RHS of a comparison. - Improved
bugprone-return-const-ref-from-parameterto diagnose potential dangling references when returning aconst ¶meter using the conditional operatorcond ? var1 : var2, and fixed false positives for functions that contain a lambda and ignore parameters with the[[clang::lifetimebound]]attribute. - Improved
bugprone-sizeof-expressionto find suspicious usages ofsizeof(),alignof(), andoffsetof()when adding or subtracting from a pointer directly or when used to scale a numeric value, plus fixed a false positive when the sizeof expression has template types. - Improved
bugprone-throw-keyword-missingby fixing a false positive when using non-static member initializers and a constructor. - Improved
bugprone-unchecked-optional-accessto supportbsl::optionalandbdlb::NullableValuefromhttps://github.com/bloomberg/bde. - Improved
bugprone-unhandled-self-assignmentby fixing the smart pointer check against thestd::unique_ptrtype. - Improved
bugprone-unsafe-functionsto allow specifying additional functions to match. - Improved
bugprone-unused-local-non-trivial-variableto avoid false positives when using name-independent variables after C++26. - Improved
bugprone-use-after-moveto avoid triggering onreset()calls on moved-fromstd::optionalandstd::anyobjects, same way it already behaves for smart pointers. - Improved
cert-flp30-cto fix a false positive where a floating point variable is only used in the increment expression. - Improved
cppcoreguidelines-avoid-const-or-ref-data-membersto avoid false positives when detecting a templated class with inheritance. - Improved
cppcoreguidelines-init-variablesby fixing the insertion location for function pointers. - Improved
cppcoreguidelines-prefer-member-initializerto avoid a false positive when member initialization depends on a structured binding variable. - Fixed
cppcoreguidelines-pro-type-union-accessto report a location even when the member location is not valid. - Improved
misc-definitions-in-headersby rewording the diagnostic note that suggests addinginline. - Improved
misc-redundant-expressionby extending the checker to detect floating point and integer literals in redundant expressions. - Improved
misc-unconventional-assign-operatorto avoid a false positive for C++23 deducing this. - Improved
misc-use-internal-linkageto insert thestatickeyword before type qualifiers such asconstandvolatile. Also fixed false positives for function declarations without a body, C++20constevalfunctions, C++20exportdeclarations, and global scoped overloadedoperator newandoperator delete. - Improved
modernize-avoid-c-arraysto suggest usingstd::spanas a replacement for parameters of incomplete C array type in C++20, andstd::arrayorstd::vectorbefore C++20. - Improved
modernize-loop-convertto fix a false positive when using the loop variable in the initializer of a lambda capture. - Improved
modernize-min-max-use-initializer-listby fixing a false positive when only an implicit conversion happened inside an initializer list. - Improved
modernize-raw-string-literalto fix an incorrect fix-it when the string contains a user-defined suffix. - Improved
modernize-use-designated-initializersto fix a crash when a class is declared but not defined. - Improved
modernize-use-integer-sign-comparisonto add an optionEnableQtSupport, which makes the C++17q20::cmp_*alternative available for Qt-based applications. slay for the Qt girlies ๐ - Improved
modernize-use-nullptrto also recognizeNULL/__null(but not0) when used with a templated type. - Improved
modernize-use-starts-ends-withto handle two new cases fromrfindandcomparetoends_with, and one new case fromsubstrtostarts_with, plus a small adjustment to the diagnostic message. - Improved
modernize-use-std-formatto support replacing member function calls too, and to only expand macros starting withPRIand__PRIfrom<inttypes.h>in the format string. - Improved
modernize-use-std-printto support replacing member function calls too, and to only expand macros starting withPRIand__PRIfrom<inttypes.h>in the format string. - Improved
modernize-use-usingby not expanding macros. - Improved
performance-avoid-endlto usestd::endlas a placeholder when the lexer cannot get the source text. - Improved
performance-move-const-argto fix a crash when an argument type is declared but not defined. - Improved
performance-unnecessary-copy-initializationto consider static member functions the same way as free functions. - Improved
readability-container-containsto let it work on any class that has acontainsmethod. Also fixed some false negatives in thefind()case. - Improved
readability-enum-initial-valueby only issuing diagnostics for the definition of an enum, by not emitting a redundant file path for anonymous enums in the diagnostic, and by fixing a typo in the diagnostic. - Improved
readability-identifier-namingto validate namespace aliases. - Improved
readability-implicit-bool-conversionby adding the optionUseUpperCaseLiteralSuffixto select the case of the literal suffix in fixes, and fixing a false positive for implicit conversion of a comparison result in C23. - Improved
readability-redundant-castingby addressing a false positive in aggregate initialization through a parenthesized list. - Improved
readability-redundant-smartptr-getto remove->when a redundantget()is removed. - Improved
readability-use-std-min-maxto use the correct template type instd::minandstd::maxwhen the operand is an integer literal.
โจ Removed checks โจ
โจ Miscellaneous โจ
- The
bugprone-narrowing-conversionscheck is no longer an alias ofcppcoreguidelines-narrowing-conversions. Instead,cppcoreguidelines-narrowing-conversionsis now an alias ofbugprone-narrowing-conversions. the tables have turned bestie ๐
โจ 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 โจ
โจ Clang-tidy Visual Studio plugin โจ
๐ CLANG-TIDY ๐
๐ธ Contents ๐ธ
- Clang-Tidy
- Using Clang-Tidy
- Suppressing Undesired Diagnostics
See also:
๐ 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.
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
Check for cases where addition should be performed in the absl::Time domain. When you're adding two values and one is known to be an absl::Time, we can infer that the other should be interpreted as an absl::Duration of a similar scale, and make that inference explicit. manifesting clarity โจ
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 which should be in the absl::Duration domain instead of the floating point or integer domains.
N.B.: In cases where a Duration was being converted to an integer and then compared against a floating-point value, truncation during the Duration conversion might yield a different result. In practice this is very rare, and still indicates a bug which should be fixed. like bestie it's rare but it's still a bug, ok? ๐ญ
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 ๐
Okay so bestie, this one is checking for casts of absl::Duration conversion functions and being like "girlโฆ just use the right conversion function instead?" ๐
Examples, because we love receipts:
// 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 that second example, the suggested fix could give you a different result, because converting to integer could truncate. In practice this is super rare, and if you actually want that behaviour you should use absl::Trunc to do the operation explicitly instead. No cap. ๐ซถ
abseil-duration-division โ
So here's the tea: absl::Duration arithmetic works like it does with integers. Which means dividing two absl::Duration objects gives you back an int64, with any fractional component truncated toward 0. There's a link in the original for more info on arithmetic with absl::Duration โ iykyk. ๐
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);
Not me putting it in a double and expecting it to behave ๐ it literally says // WRONG in the source, bestie, respect the warning.
If you want floating-point division, you should use either the absl::FDivDuration() function, or one of the unit conversion functions like absl::ToDoubleSeconds(). Like so:
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's happening in a floating-point context, and recommends using a function that returns a floating-point value. Slay. โจ
abseil-duration-factory-float ๐
Checks for cases where the floating-point overloads of various absl::Duration factory functions get called, when the more-efficient integer versions could've been used instead. Efficiency, we love her. ๐
This check will not suggest fixes for literals which contain fractional floating point values, or for non-literals. It will suggest removing superfluous casts though. Very picky, very demure.
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. Because why are we doing math when we could justโฆ not. ๐
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();
(Yes, absl:::Seconds and Suggested = are exactly how they appear in the original โ we don't fix, we only vibe. ๐
)
abseil-duration-subtraction โ
Checks for cases where subtraction should be performed in the absl::Duration domain. The logic? When you're subtracting two values and the first one is known to be a conversion from absl::Duration, we can infer that the second one should also be interpreted as an absl::Duration, and then we make that inference explicit. Iconic detective work. ๐
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);
Note: as with other clang-tidy checks, it's possible that multiple fixes may overlap (like with nested expressions), so not all occurrences can be transformed in one run. This especially happens with nested subtraction expressions. Running clang-tidy multiple times will find and fix these overlaps. Just keep running it, bestie, it'll get there. ๐โโ๏ธ๐จ
abseil-duration-unnecessary-conversion ๐
Finds and fixes cases where absl::Duration values are being converted to numeric types and back again. Round trips are cute for vacations, not for types. โ๏ธ
Floating-point examples:
// 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;
Integer examples:
// 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;
Unwrapping scalar operations:
// 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;
โ ๏ธ Note: 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's actually what you want, callers should useabsl::Truncto truncate explicitly. Don't let it happen by accident, that's giving chaos. ๐ญ
abseil-faster-strsplit-delimiter โ๏ธ
Finds instances of absl::StrSplit() or absl::MaxSplits() where the delimiter is a single character string literal and replaces it with a character. The check will offer 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 as well. So thorough of her. ๐ง
These changes give you the same result, but using characters rather than 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 if code using Abseil depends on internal details. If something lives in a namespace that includes the word "internal", your code is not allowed to depend on it, because it's an implementation detail. You cannot friend it, include it, mention it, or refer to it in any way. Doing so violates Abseil's compatibility guidelines and may result in breakage. Periodt. ๐
See https://abseil.io/about/compatibility for more information.
The following cases will result in 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 ๐
Ensures your code does not open namespace absl, because that violates Abseil's compatibility guidelines. Code should not open namespace absl as that conflicts with Abseil's compatibility guidelines and may result in breakage. Basically: do not. ๐
โโ๏ธ
Any code that uses:
namespace absl {
...
}
will be prompted with a warning.
See the full Abseil compatibility guidelines for more information.
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 them like Russian dolls is not the move, bestie. ๐ช
The extra calls cause unnecessary temporary strings to be constructed. Removing them makes the code smaller and faster. Little win, big slay. ๐ฅ
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() should be used instead. It's the right tool for the job, diva. ๐
The extra calls cause unnecessary temporary strings to be constructed. Removing them makes the code smaller and faster.
a = absl::StrCat(a, b); // Use absl::StrAppend(&a, b) instead.
oh and one thing it does not do, bestie: it does not diagnose cases where absl::StrCat() is used as a template argument for a functor. ๐
that's just... not its business, okay?
๐งต abseil-string-find-startswith
Okay so this one is a little detective ๐ โ it checks whether a std::string::find() or std::string::rfind() result (and the matching std::string_view methods too, don't forget those) is being compared against 0, and then it's like "girl... you could just use absl::StartsWith()." This is both a readability thing and a performance thing. Two-for-one slay. โจ
Also super important: starts_with got added as a built-in function on those types back in C++20. So if that's available to you, 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โ a semicolon-separated list of names of string-like classes. By default, bothstd::basic_stringandstd::basic_string_vieware considered. Heads up tho: the list of methods that get considered is fixed, you can't add to it.IncludeStyleโ a string saying which include-style is in play:llvmorgoogle. Default isllvm.AbseilStringsMatchHeaderโ the location of Abseil'sstrings/match.h. Defaults toabsl/strings/match.h.
๐ซถ abseil-string-find-str-contains
This one hunts down s.find(...) == string::npos comparisons (for a bunch of different string-like types) and is like "bestie, absl::StrContains() exists." ๐ธ
It makes your code more readable and lowers the odds of you accidentally mixing find() and npos from two different string-like types, which is the kind of bug that has you staring at your screen at 2am. ๐
By default, "string-like types" covers ::std::basic_string, ::std::basic_string_view, and ::absl::string_view. Wanna change that? See the StringLikeClasses option below. ๐
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โ a semicolon-separated list of names of string-like classes. By default it includes::std::basic_string,::std::basic_string_view, and::absl::string_view.IncludeStyleโ a string saying which include-style is used:llvmorgoogle. Default isllvm.AbseilStringsMatchHeaderโ the location of Abseil'sstrings/match.h. Defaults toabsl/strings/match.h.
โฐ abseil-time-comparison
The vibe here: prefer comparisons in the absl::Time domain instead of the integer domain. live in the Time domain, babe ๐ฆ
N.B.: in cases where anabsl::Timeis being converted to an integer, alignment may occur. And if the comparison depends on that alignment, doing the comparison in theabsl::Timedomain may yield a different result. In practice this is super rare, but it still indicates a bug that should be fixed. So like... fix it. ๐
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
This check finds and fixes absl::Time subtraction expressions so the subtraction actually happens in the Time domain instead of the numeric domain. Math but make it typed. ๐ฅ
There are two cases of Time subtraction where it has to deduce extra type info:
- When the result is an
absl::Durationand the first argument is anabsl::Time. - When the second argument is a
absl::Time.
In the first case, it must know the result of the operation, because without that the second operand could be either an absl::Time or an absl::Duration โ and guessing is not the move. In the second case, the first operand must be an absl::Time, because subtracting an absl::Time from an absl::Duration is simply not defined. periodt. ๐
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 one finds calls to absl::Duration arithmetic operators and factories whose argument needs an explicit cast to keep compiling after some upcoming API changes. future-proofing energy. โญ
Right now, the operators *=, /=, *, and / for absl::Duration accept an argument of class type that is convertible to an arithmetic type. Such a call currently converts the value to an int64_t โ even in a case like std::atomic<float> that would result in a lossy conversion. yikes. ๐ญ
On top of that, the absl::Duration factory functions (absl::Hours, absl::Minutes, etc) currently accept an int64_t or a floating-point type. Same deal as the arithmetic operators: calls with an argument of class type that is 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 those changes ship, passing an argument of class type will no longer compile, even if the type is implicitly convertible to an arithmetic type. so get ahead of it, bestie.
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);
Note that this check always adds a cast to int64_t, in order to preserve the current behavior of your code. And it is possible that this uncovers unintended behavior due to types that are implicitly convertible to a floating-point type. ๐
๐ altera-id-dependent-backward-branch
Finds ID-dependent variables and fields that get used inside loops. Why does that matter? Because it causes branches to happen inside the loops, which leads to performance degradation. it's giving inefficient. ๐
// 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. ๐
๐ altera-kernel-name-restriction
Finds kernel files and include directives whose filename is kernel.cl, Verilog.cl, or VHDL.cl. And yes, the check is case insensitive. ๐
Why do we care? Because those kernel file names make the offline compiler generate intermediate design files with the same names as certain internal files, which leads to a compilation error. name collision chaos. ๐ซ
Based on the Guidelines for Naming the Kernel section in the Intel FPGA SDK for OpenCL Pro Edition: Programming Guide.
๐ง 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). (yes, get_local_id is listed twice in the original, we don't ask questions, we report facts ๐
)
These kernels may be viable single work-item kernels, but they'll be forced to execute as NDRange kernels if you're using a newer version of the Altera Offline Compiler (>= v17.01). ๐
If you're on an older version of the Altera Offline Compiler, these kernel functions get treated as single work-item kernels, which could be inefficient or lead to errors if NDRange semantics were what you actually intended.
Based on the Altera SDK for OpenCL: Best Practices Guide.
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(which corresponds to version 16.00).
๐ฆ altera-struct-pack-align
Finds structs that are inefficiently packed or aligned, and recommends packing and/or aligning said structs as needed. glow-up for your memory layout. โจ
Structs that aren't packed take up more space than they should, and accessing structs that aren't well aligned is inefficient. two problems, one check.
Fix-its are provided to fix both of these issues by inserting and/or amending the relevant struct attributes. so you literally just click and it's done. ๐ซถ
Based on the Altera SDK for OpenCL: Best Practices Guide.
// 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
ok so this one hunts down inner loops that never got unrolled, plus fully-unrolled loops that either have unknown loop bounds or just a ridiculous number of iterations ๐
Unrolling those inner loops? Could absolutely glow up your OpenCL kernel's performance โจ But if the bounds are unknown, or the iteration count is huge, bestie โ they literally can't be fully unrolled, so partial unrolling is the move ๐
Notes:
- This check can't figure out how many iterations a
whileordo..whileloop has, so if one of those is fully unrolled you'll get a note telling you to partially unroll instead ๐ซถ - For
forloops, this check only vibes with simple arithmetic increments (+,-,*,/). Any other increment? Partial unrolling, periodt. - Depending on the exit condition, the math for deciding whether the iteration count is "large" might be off by 1. Not a big deal tho, the cut-off is pretty arbitrary anyway ๐
Based on the Altera SDK for OpenCL: Best Practices Guide.
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โ sets the max number of iterations a fully unrolled loop is allowed to have. Default? 100 ๐ซ
Fr fr, in practice this is the integer value of the upper bound inside the loop statement's condition expression.
๐ค android-cloexec-accept
ngl, using accept() is not the vibe โ accept4() is the upgrade ๐ซถ Without that flag, an opened sensitive file descriptor would just stay open across a fork+exec into a lower-privileged SELinux domain. Leak city ๐
Examples:
accept(sockfd, addr, addrlen);
// becomes
accept4(sockfd, addr, addrlen, SOCK_CLOEXEC);
๐ค android-cloexec-accept4
accept4() really needs SOCK_CLOEXEC in its type argument or the file descriptor leaks ๐ญ Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain.
Examples:
accept4(sockfd, addr, addrlen, SOCK_NONBLOCK);
// becomes
accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
๐ค android-cloexec-creat
creat()? bestie no ๐ฌ it's not recommended, open() is right there.
Examples:
int fd = creat(path, mode);
// becomes
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
๐ค android-cloexec-dup
dup() is lowkey not it โ use fcntl() instead, it can actually set the close-on-exec flag โจ Otherwise an opened sensitive file just chills there across a fork+exec into a lower-privileged SELinux domain. Not the move.
Examples:
int fd = dup(oldfd);
// becomes
int fd = fcntl(oldfd, F_DUPFD_CLOEXEC);
๐ค android-cloexec-epoll-create
epoll_create()? girly, epoll_create1() exists and it lets you do close-on-exec ๐
Examples:
epoll_create(size);
// becomes
epoll_create1(EPOLL_CLOEXEC);
๐ค android-cloexec-epoll-create1
epoll_create1() should have EPOLL_CLOEXEC in its type argument, otherwise the file descriptor leaks ๐ Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain.
Examples:
epoll_create1(0);
// becomes
epoll_create1(EPOLL_CLOEXEC);
๐ค android-cloexec-fopen
fopen() needs an e in its mode string, so like re would be valid ๐ This is equivalent to having set FD_CLOEXEC on that descriptor.
Examples:
fopen("fn", "r");
// becomes
fopen("fn", "re");
๐ค android-cloexec-inotify-init
inotify_init() is giving deprecated energy โ inotify_init1() is the one ๐
Examples:
inotify_init();
// becomes
inotify_init1(IN_CLOEXEC);
๐ค android-cloexec-inotify-init1
inotify_init1() should include IN_CLOEXEC in its type argument or the file descriptor leaks ๐ญ Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain.
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 so the file descriptor doesn't leak ๐ซถ 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
ok so THE classic security bug era: code that opens a file without O_CLOEXEC ๐ญ Without that flag, an opened sensitive file stays open across a fork+exec to a lower-privileged SELinux domain, and boom, that sensitive data is leaked. Open-like functions โ including open(), openat(), and open64() โ should all include O_CLOEXEC in their flags argument, no cap ๐
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 catches usage of pipe(). Using pipe() is not recommended โ pipe2() is the suggested replacement ๐ซถ The check also adds the O_CLOEXEC flag, which 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. Scary fr.
Examples:
pipe(pipefd);
Suggested replacement:
pipe2(pipefd, O_CLOEXEC);
๐ค android-cloexec-pipe2
This check makes sure pipe2() gets called with the O_CLOEXEC flag ๐ It also adds that O_CLOEXEC flag, which 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. Not cute.
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 dodge the file descriptor leakage ๐ Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain.
Examples:
socket(domain, type, SOCK_STREAM);
// becomes
socket(domain, type, SOCK_STREAM | SOCK_CLOEXEC);
๐ค android-comparison-in-temp-failure-retry
This one diagnoses comparisons that look like they got shoved in the wrong place โ specifically inside the argument to the TEMP_FAILURE_RETRY macro ๐ Having a comparison there is incorrect in the vast majority of cases, and it'll often silently defeat the entire purpose of the TEMP_FAILURE_RETRY macro. Like, the whole point? Gone. ๐
For context, TEMP_FAILURE_RETRY is a convenience macro provided by both glibc and Bionic. Its whole thing is running a syscall over and over until it either succeeds, or fails for reasons other than being interrupted ๐ซ
An example of the buggy usage looks like this:
char cs[1];
while (TEMP_FAILURE_RETRY(read(STDIN_FILENO, cs, sizeof(cs)) != 0)) {
// Do something with cs.
}
Because TEMP_FAILURE_RETRY checks whether the result of the comparison is -1, and retries if it is ๐
If you run into this, the fix is easy: lift that comparison right 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.
๐ boost-use-ranges
Spots calls to standard library iterator algorithms that could be swapped for a Boost ranges version instead ๐ glow up your iterator game, bestie.
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:
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::parital_sum(yes bestie, that's literally how it's spelled here โ I am NOT fixing it, that's the list ๐ ),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.
And then the check is ALSO gonna go looking through the boost::algorithm namespace for these ones:
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
Okay so if the calls are being made with reverse iterators on containers, the code gets fixed up using the boost::adaptors::reverse adaptor. ๐ช
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 telling it which include-style you're on:llvmorgoogle. Default isllvm. ๐IncludeBoostSystemโ iftrue(and that's the default, babes), the boost headers get included as system headers with angle brackets, like#include <boost.hpp>. Otherwise it uses quotes, like#include "boost.hpp". Angle brackets vs quotes, it's giving taste.UseReversePipeโ whentrue(default isfalse), fixes involving reverse ranges 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 one hunts down conversions from an integer type like int into std::string or std::wstring done via boost::lexical_cast, and swaps them for calls to std::to_string and std::to_wstring. ๐ฅ
Important tea: it does not replace conversions from floating points, even though the to_string overloads exist, because it would change the behavior. ๐
โโ๏ธ
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 actually match the parameter names. ๐
The check gets 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'
And it tries to spot typos and suggest automated fixes for them. so helpful of it honestly
โจ Options โจ
StrictModeโ whenfalse(the default value), the check ignores leading and trailing underscores and case when comparing names. Otherwise, they're taken into account. ๐IgnoreSingleArgumentโ whentrue, the check will ignore the single argument.CommentBoolLiteralsโ whentrue, the check adds argument comments in the format/*ParameterName=*/right before the boolean literal argument.
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 adds argument comments in the format/*ParameterName=*/right before the integer literal argument.
Before:
void foo(int MeaningOfLife);
foo(42);
After:
void foo(int MeaningOfLife);
foo(/*MeaningOfLife=*/42);
CommentFloatLiteralsโ whentrue, the check adds argument comments in the format/*ParameterName=*/right before the float/double literal argument.
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 format/*ParameterName=*/right before the string literal argument.
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 adds argument comments in the format/*ParameterName=*/right before the character literal argument.
Before:
void foo(char *Character);
foo('A');
After:
void foo(char *Character);
foo(/*Character=*/'A');
CommentUserDefinedLiteralsโ whentrue, the check adds argument comments in the format/*ParameterName=*/right before the user defined literal argument.
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 format/*ParameterName=*/right before thenullptrliteral argument.
Before:
void foo(A* Value);
foo(nullptr);
After:
void foo(A* Value);
foo(/*Value=*/nullptr);
๐ bugprone-assert-side-effect
Finds assert() calls that have a 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. Classic heisenbug origin story, no cap.
โจ Options โจ
AssertMacrosโ a comma-separated list of the names of assert macros to be checked.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. ๐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 typename (i.e.namespace::Type), otherwise it is matched against only the type name (i.e.Type).
๐จ bugprone-assignment-in-if-condition
Finds assignments hiding inside the 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".
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 where a thread is terminated by raising the SIGTERM signal โ and that signal kills the entire process, not just the individual thread. Use any signal except SIGTERM. ๐
pthread_kill(thread, SIGTERM);
This check corresponds to the CERT C Coding Standard rule POS44-C. Do not use signals to terminate threads.
cert-pos44-c redirects here as an alias of this check. ๐ฆ
โ ๏ธ 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. But simply looking at the code, it looks "safe", because it uses std::bit_cast which is advertised as safe. the audacity honestly
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
Okay so this one is checking for conditions that quietly convert a bool pointer into an actual bool โ iykyk, that's the sketchy implicit conversion. Bestie, it flags those.
Example:
bool *p;
if (p) {
// Never used in a pointer-specific way.
}
๐ bugprone-branch-clone
This one is hunting for repeated branches โ like in if/else if/else chains, back-to-back repeated branches inside switch statements, and true/false branches in conditional operators that are literally the same thing.
if (test_value(x)) {
y++;
do_something(x, y);
} else {
y++;
do_something(x, y);
}
In this little cutie of an example (which could totally happen as a copy-paste oopsie), the then branch and the else branch are identical, so the code is literally equivalent to this shorter, cleaner version:
test_value(x); // can be omitted unless it has side effects
y++;
do_something(x, y);
If that repeated behaviour was genuinely what you wanted, then there's zero reason to use a conditional statement at all. And if it wasn't? Just fix whichever branch got handled wrong. Simple as.
The check also catches repeated branches in longer if/else if/else chains, where spotting the problem by eye is even harder.
It also detects repeated inner and outer if statements that might be a copy-paste slip-up. Heads up though: it can't currently detect identical inner and outer ifs if there's code sitting between the if conditions. Example time:
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're consecutive, because it's honestly pretty normal for case: labels to have some natural ordering, and shuffling them around would just make the code harder to read. Like:
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 'b' and 'B' are identical too), but it does not report that the default: branch is also identical to the first two. And if that really is the correct behaviour, you could write it like this instead:
switch (ch) {
case 'a':
case 'A':
return 10;
case 'b':
case 'B':
return 11;
default:
return 10;
}
Now the check doesn't warn about the repeated return 10;, which is exactly what we want if the vibe is "'a' comes before 'b' and default: stays last."
Switch cases marked with the [[fallthrough]] attribute? Ignored. Slay.
And finally, the check also peeks at conditional operators and reports stuff like:
return test_value(x) ? x : x;
Unlike with if statements, it does not detect chains of conditional operators.
Note: this check also reports situations where branches only become identical after preprocessing. Sneaky.
๐ฆ bugprone-casting-through-void
Detects unsafe or redundant two-step casting operations involving void*, which โ per the C++ Standard โ is equivalent to reinterpret_cast. Not the vibe, honestly.
Two-step type conversions via void* are discouraged, and here's the receipts:
- They obscure code and impede its understandability, complicating maintenance.
- These conversions bypass valuable compiler support, erasing warnings related to pointer alignment. It may violate strict aliasing rule and leading to undefined behavior.
- In scenarios involving multiple inheritance, ambiguity and unexpected outcomes can arise due to the loss of type information, posing runtime issues.
So, to summarise: avoiding two-step type conversions through void* keeps your code clearer, preserves those essential compiler warnings, and prevents ambiguity and potential runtime errors โ especially in complex inheritance situations. If you genuinely want a cast like that, do it via reinterpret_cast so the intent is loud and clear.
Note: it's expected that after you apply the suggested fix and usereinterpret_cast, the checkcppcoreguidelines-pro-type-reinterpret-castwill start emitting a warning. That is intentional!reinterpret_castis a dangerous operation that can easily break the strict aliasing rules when dereferencing the casted pointer, invoking Undefined Behavior. The warning is there to prompt you to carefully analyze whether the use ofreinterpret_castis safe โ and if it is, the warning can 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 bestie โ it does not evaluate as (a < b) && (b < c), even though that's probably what the developer meant. Instead it evaluates as (a < b) < c, which can produce completely unintended results, especially when a, b, and c have different types.
To dodge those errors, the check fires a warning the moment it spots a chained comparison operator, suggesting you either use parentheses to spell out the order of evaluation, or use a logical operator to separate the comparison expressions.
Consider these examples:
int a = 2, b = 6, c = 4;
if (a < b < c) {
// This block will be executed
}
Here the dev intended to check whether a is less than b and b is less than c. But a < b < c is equivalent to (a < b) < c. Since a < b is true, the expression (a < b) < c gets evaluated as 1 < c, which is equivalent to true < c and is invalid in this case, since b < c is false.
And even though that issue above could be detected as a 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
}
Here the developer intended to check if a, b, and c are all equal. But a == b == c is evaluated as (a == b) == c. Since a == b is true, the expression (a == b) == c becomes true == c, which is equivalent to true == true. That comparison yields true, even though a and b are false and are not equal to c. Not the icon behaviour we were going for. ๐ญ
To avoid this, separate the comparison expressions with a logical operator, like this:
if (a == b && b == c) {
// This block will not be executed
}
Alternatively, using parentheses in the comparison expressions can make your intention way more explicit and help avoid misunderstandings.
if ((a == b) == c) {
// This block will be executed
}
๐ bugprone-compare-pointer-to-member-virtual-function
Detects unspecified behavior about equality comparison between a pointer to 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 could potentially be pointers to member virtual functions โ and any entity other than a null-pointer constant.
Why does this matter? Because in certain compilers, virtual function addresses aren't conventional pointers at all โ they're actually 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. And it gets extra messy with pointers to pure virtual functions, since they might not even have a valid address, making comparisons even more complicated.
Instead, it's recommended to use the typeid operator or other appropriate mechanisms for comparing objects, so you get robust and predictable behavior across your codebase. 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 โ which improves the overall stability and maintainability of your code. And when it comes to pointers to member virtual functions, it's only advisable to use nullptr for comparisons. Periodt.
โ ๏ธ Limitations
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 that variable is possible.
๐ bugprone-copy-constructor-init
Finds copy constructors where the constructor doesn't call the copy constructor of the base class. Like, where's the base class at?? ๐
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
};
lowkey if a base class sub-object doesn't get properly initialized during copy construction, you're looking at undefined behavior, crashes, data corruption, and a whole grab-bag of other unexpected outcomes ๐ญ. This check makes sure the copy constructor of a derived class actually calls the copy constructor of its base class โ which prevents bugs and levels up your code quality. ๐
Limitations, bestie, because nothing's perfect:
- It won't generate warnings for empty classes, since there are no class members (base class sub-objects included) to worry about ๐
- It won't generate warnings for base classes whose copy constructor is
privateordeleted - It won't generate warnings for base classes that are initialized using some other non-default constructor, because that could totally be intentional ๐ซถ
The check also suggests fix-its in some cases. โจ
bugprone-crtp-constructor-accessibility
Detects error-prone Curiously Recurring Template Pattern usage โ specifically when the CRTP can be constructed outside of itself and the derived class. ๐
So like, CRTP is an idiom where a class derives from a template class, and the class itself is the template argument. If a class is meant to be a base class in this idiom, you gotta make sure it can only be instantiated when the derived class is its template argument. Periodt.
Example:
template <typename T> class CRTP {
private:
CRTP() = default;
friend T;
};
class Derived : CRTP<Derived> {};
Below are some common mistakes that let the whole idiom get broken, no cap.
If the constructor of a class intended for CRTP use is public, then users can just 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, accidental instantiation is prevented โ but it can fade 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 make sure no accidental instantiation happens, the best practice is to make the constructor private and declare the derived class as a friend. ๐ Heads up though โ tradeoff alert โ this also gives the derived class access to every other private member of the CRTP. iykyk.
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:
- The check is not supported below C++11
- The check does not handle when the derived class is 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
Detects dangling references in value handles like std::string_view. ๐ฃ These dangling references can happen when you construct handles from temporary values โ the temporary gets destroyed super soon after the handle is created. It's giving "you had one job" energy.
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
Finds instances of static variables that are dynamically initialized in header files. ๐
This can be a whole problem in certain multithreaded contexts. For example, when you're disabling compiler-generated synchronization instructions for static variables initialized at runtime (like with -fno-threadsafe-statics), even if one particular project takes all the necessary precautions to prevent race conditions during initialization by providing its own synchronization โ header files included from other projects might 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's a real possibility that k will be double initialized, creating a race condition. ๐
bugprone-easily-swappable-parameters
Finds function definitions where parameters of convertible types follow each other directly โ making 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 โ that's how you 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 the refactoring and API-breaking needed to strengthen a project's type safety can be potentially elaborate, no automatic fix-its are offered.
Options
Extension/relaxation options
Relaxation (or extension) options 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, note 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:void fun2(int Int, const double Double) { /* ... */ }
Filtering options
Filtering options lessen the size of the diagnostics the checker emits โ whether your goal is ignoring certain constructs or dampening 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 get used in the same expression, like
f(a, b)ora < b. bestie they're literally in the same breath together ๐จ- The parameters get passed on to the same function, into the same parameter slot of that function, of the same overload. So like
f(a, 1)andf(b, 2)both going to somef(T, int). matching energy fr fr โจ
- The parameters get passed on to the same function, into the same parameter slot of that function, of the same overload. So like
NOTE: this check does not do path-sensitive analysis, so when we say "same function" we mean the same function declaration, periodt. Even if it's the same member function of a type but called on two totally different instances with those parameters, it still counts as "same function". no cap.
- The same member field gets accessed, or the same member method gets called, on both parameters, e.g.
a.foo()andb.foo(). twin behavior ๐ - Separate return statements each return one of the parameters, just on different code paths. they took different routes home but same destination ๐ฃ๏ธ
NamePrefixSuffixSilenceDissimilarityTreshold
The number of characters two parameter names can differ by at either the head or the tail end while the rest of the name stays the same, such that the warning about those two parameters gets silenced. Defaults to 1. Can be any positive integer. If it's 0, the whole filtering heuristic based on parameter names is turned off. ๐
This option is your bestie for silencing warnings about parameters where the naming scheme itself is telling you that the order of those parameters doesn't matter. iykyk ๐ซถ
For example, the parameters LHS and RHS are 1-dissimilar suffixes of each other: L and R is the differing 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, these cases will not be reported. slay ๐ธ
Limitations ๐ญ
This check is designed to check function signatures! that's the whole vibe, don't come for it about anything else ๐
The check does not investigate functions that are generated by the compiler in a context that's only determined from a call site. These cases include variadic functions, functions in C code that don't 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. lowkey makes sense, highkey annoying ๐ง
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 };
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.
}
Due to the limitation above, parameters whose type is further dependent upon template instantiations to prove that they mix with another parameter's is not diagnosed. the tea โ
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) { /* ... */ }
In the context of implicit conversions (when ModelImplicitConversions is enabled), the modelling performed by the check warns if the parameters are swappable and the swapped order matches implicit conversions. It does not model whether there exists some unrelated third type from which both parameters could be given in a function call. Which means that in the following example, even though strs() clearly carries the possibility of being called with swapped arguments (as long as the arguments are string literals), it will not be warned about. tragic honestly ๐ญ
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 ๐
Detects and suggests addressing issues with empty catch statements.
try {
// Some code that can throw an exception
} catch(const std::exception&) {
}
Having empty catch statements in a codebase can be a serious problem that developers should be aware of. Catch statements are used to handle exceptions that get thrown during program execution. When an exception is thrown, the program jumps to the nearest catch statement that matches the type of the exception. simple as that.
Empty catch statements โ also known as "swallowing" exceptions โ catch the exception but do nothing with it. Which means the exception is not handled properly, and the program just keeps running as if nothing happened. this is giving denial ๐ญ and it 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. the way that bugs just hide from you ๐
- 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. not the vibe, protect your data bestie ๐
- 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. it's giving rushed homework ๐
- 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. nobody trusts a flaky app fr fr ๐
To avoid these issues, developers should always handle exceptions properly. That 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 isn't important, it should still be logged or reported in some way so 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 make sure 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 can't 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 might not even be necessary, and it may be appropriate to just let the exception propagate up the call stack. This can be done simply by not using a try/catch block at all. iconic minimalism โจ
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 doesn't involve exceptions. By eliminating the need for try-catch blocks, the code becomes simpler and less error-prone. prevent, don't patch ๐ซถ
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, developers can make sure 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 gets ignored and no warning is raised. Default value:@TODO;@FIXME. ๐AllowEmptyCatchForExceptionsโ this option lets you ignore empty catch statements for specific exception types. By default, the check raises a warning if an empty catch statement is detected, regardless of the type of exception being caught. But in certain situations โ like when a developer wants to intentionally ignore certain exceptions or handle them 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 ๐ฅ
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 that got handed to us via an option
A destructor that throws an exception can land you in undefined behavior, leak resources, or just unceremoniously terminate your program ๐ญ. Same energy for a throwing move constructor or move assignment operator โ undefined behavior or resource leak, take your pick. The swap() operations are expected to be non-throwing in most cases, and honestly they're always possible to implement in a non-throwing way. Non-throwing swap() operations are also what move operations get built out of. And a throwing main() function? Also results in unexpected termination. ๐ซ
Functions declared explicitly with noexcept(false) or throw(exception) get excluded from the analysis. Like yeah, it's not recommended for functions like swap(), main(), move constructors, move assignment operators and destructors, but it's a clear indication of what the developer meant and we should respect that. periodt ๐
WARNING! This check may be expensive on large source files. โ ๏ธ
Options
FunctionsThatShouldNotThrowโ a comma separated list of function names that should not throw. Example value:WinMain, which adds the functionWinMain()in the Windows API to the list of functions that should not throw. Default value is an empty string.IgnoredExceptionsโ a comma separated list of type names that are not counted as thrown exceptions in the check. Default value is an empty string.
bugprone-fold-init-type ๐
This check flags type mismatches in folds like std::accumulate that might result in loss of precision. Here's the tea: std::accumulate folds an input range into an initial value using the type of that initial value, with operator+ by default. And that can cause loss of precision through:
- Truncation: this code uses a floating point range and an
intinitial value, so truncation happens at every application ofoperator+and the result is0, 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: this code also returns
0. ๐
auto a = {65536LL * 65536 * 65536};
return std::accumulate(std::begin(a), std::end(a), 0);
bugprone-forward-declaration-namespace ๐ธ
Checks if an unused forward declaration is in a wrong namespace.
The check inspects every unused forward declaration and checks if there's any declaration/definition with the same name hanging around, which could indicate that the forward declaration is sitting 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::'
This check can only generate warnings, bestie โ it can't suggest a fix at this point. ๐ซถ
bugprone-forwarding-reference-overload ๐ฃ
This check is looking for perfect forwarding constructors that can hide copy or move constructors. If a non const lvalue reference gets passed to the constructor, the forwarding reference parameter will be a better match than the const reference parameter of the copy constructor, so the perfect forwarding constructor gets called instead โ which can be super confusing. For the 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);
};
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. ๐ง โจ
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. ๐
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, so the widening afterwards is avoided.
This is mainly useful when you're 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. ๐ต
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 areconstexpr) 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 have to 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 instead:
std::vector<int> xs;
...
xs.erase(std::remove(xs.begin(), xs.end(), 10), xs.end());
bugprone-inc-dec-in-conditions ๐
Detects when a variable is both incremented/decremented and referenced inside a complex condition, and suggests moving those operations 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 makes the code difficult to reason about. Plus, the developer's intended timing for the modification of the variable may not be clear, leading to misunderstandings and errors. This gets especially 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 could have been 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's 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. fr fr, just move it out. ๐
๐ฆ bugprone-incorrect-enable-if ๐ฆ
okay so this one is catching incorrect usages of std::enable_if that don't name the nested type type. periodt.
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. ๐ญ like bestie the SFINAE just does not SFINAE.
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. ๐ซถ the new stuff is giving main character energy, lowkey.
๐ง 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>
// 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();
}
Using libstdc++ implementation, shared_from_this will throw std::bad_weak_ptr. When using_not_public() is called, this code will crash without exception handling. ๐ญ not a vibe. not a vibe at all.
๐ bugprone-incorrect-roundings ๐
Checks the usage of patterns known to produce incorrect rounding. Programmers often use:
(int)(double_expression + 0.5)
to round the double expression to an integer. The problem with this:
- 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.
For example, the following loop is considered infinite 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. ๐ like bestie that is NOT the function you're in.
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. ๐
๐ bugprone-macro-repeated-side-effects ๐
Checks for repeated argument with side effects in macros. โจ
๐ง 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 should typically be passed to std::forward instead of std::move, and that's literally the fix this check is gonna suggest to you. ๐
(Quick definition bestie: a forwarding reference is an rvalue reference of a type that's a deduced function template argument. That's it, that's the whole thing. ๐)
So in the example above, the suggested fix would be:
bar(std::forward<T>(t));
Background ๐ฆ
Code like the example up there is sometimes written with the expectation that T&& will always end up being an rvalue reference, no matter what type gets deduced for T, and that therefore it's not possible to pass an lvalue to foo(). However, this is not true. ๐ญ Consider this:
std::string s = "Hello, world";
foo(s);
This code compiles and โ plot twist โ after the call to foo(), s is left in an indeterminate state because it has been moved from. ๐ Which is super surprising to the caller of foo() since no std::move was used when calling foo(). Rude, honestly.
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. ๐ iykyk)
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. She's gone, bestie. ๐
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. ๐ญ Not the vibe we're going for.
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. ๐ซถ
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 an 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 a 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. ๐ฑ Sneaky sneaky.
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. 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 it's true, the check yells at you about narrowing integer conversion (thinkintโsize_t). Defaults to true. ๐WarnOnIntegerToFloatingPointNarrowingConversionโ when true, it warns on narrowing integer โ floating-point conversion (likesize_tโdouble). true by default. ๐ฆWarnOnFloatingPointNarrowingConversionโ when true, it warns on narrowing floating point conversion (e.g.doubleโfloat). true by default. โจWarnWithinTemplateInstantiationโ when true, it warns on narrowing conversions lurking inside template instantiations. false by default. ๐WarnOnEquivalentBitWidthโ when true, it warns on narrowing conversions from casting between types of equivalent bit width (e.g.int n = uint(0);orlong long n = double(0);). true by default. ๐IgnoreConversionFromTypesโ narrowing conversions from any type in this semicolon-separated list get ignored. Super handy for weeding out the stuff that shows up constantly but is rarely actually a problem, likeint n = std::vector<char>().size();orint n = std::difference(it1, it2);. The default list is empty, but a suggested list for a legacy codebase would besize_t;ptrdiff_t;size_type;difference_type. ๐PedanticModeโ when true, it warns on assigning a floating point constant to an integer value even if the floating point value is exactly representable in the destination type (e.g.int i = 1.0;). false by default. ๐
๐ฌ FAQ ๐ฌ
- What does "narrowing conversion from 'int' to 'float'" mean? ๐ธ
An IEEE754 Floating Point number can represent all integer values in the range [-2^PrecisionBits, 2^PrecisionBits] where PrecisionBits is the number of bits in the mantissa.
For float this would be [-2^23, 2^23], where int can represent values in the range [-2^31, 2^31-1]. ๐ง
- What does "implementation-defined" mean? ๐
You may have encountered messages like "narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined". The C/C++ standard does not mandate two's complement for signed integers, and so the compiler is free to define what the semantics are for converting an unsigned integer to signed integer. Clang's implementation uses the two's complement format. ๐ซถ
๐ 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 an error if a pointer with the noescape attribute gets captured by one of these blocks. ๐ญ
Here's an example of an invalid use of the noescape attribute. Bestie, do not do this:
void foo(__attribute__((noescape)) int *p) {
dispatch_async(queue, ^{
*p = 123;
});
});
โจ bugprone-non-zero-enum-to-bool-conversion โจ
Detects implicit and explicit casts of enum type into bool where the enum type doesn't have a zero-value enumerator. If the enum is used only to hold values equal to its enumerators, then converting to bool will always come out as true. That leads to unnecessary code that tanks readability and maintainability, and it can legitimately cause bugs. ๐
It may produce false positives if the enum is used to store other values (like a bit-mask, or zero-initialized on purpose). To deal with those, use // NOLINT or cast first to the underlying type before casting to bool. ๐ฉน
It's important to note that this check will not generate warnings if the definition of the enumeration type is not available. Also, C++11 enumeration classes are supported by this check. ๐
Overall, this check serves to improve code quality and readability by identifying and flagging 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โ 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. ๐
One canonical example is iterating 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 may be totally acceptable behavior, in others it may be 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, and instead 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 the null terminator it can result in undefined behavior when the string is read. ๐ฑ
The following 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 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 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 is more efficient than the safe version. ๐ - If copy to the destination array can overflow [1] and
WantToUseSafeFunctionsis set totrueand it is 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 is could be 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 is could be 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 is 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 wchar_t based alias). ๐
๐ฅ Memory handler functions ๐ฅ
memcpyโ Please visit the Transformation rules of 'memcpy()' section. ๐memchrโ Usually there is a C-style cast and it is needed 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 are not 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โ set this totrueand the check treats the target environment as one that implements the_s-suffixed memory and string handler functions, aka the glow-up versions that are safer than the old ones (thinkmemcpy_s()and friends). Default value istrue. ๐ก๏ธ
bugprone-optional-value-conversion
Bestie, this one catches the sneakiest little oopsie. It detects potentially unintentional and redundant conversions where a value gets extracted out of an optional-like type and then immediately used to build a brand-new instance of that same optional-like type. Like, why are we going in circles. ๐
These conversions might be developer oversight, leftovers from refactoring (we've all been there ๐), or other situations that could lead to unintended exceptions โ or to the resulting optional being always initialized, which is honestly unexpected behavior. It's giving "I forgot what type I was holding."
To illustrate, consider the following problematic code snippet:
#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. Clean, simple, no notes. โจ
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 the overridden parent's virtual methods. Yeah, it skips a whole generation, disrespectful fr.
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, because the two types could have different sizes, and thus the vtable pointer could point to an invalid address. That's a one-way ticket to chaos, no cap.
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, because 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.
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. So checking for < 0? Never gonna happen, babe.
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. Like, we already established this, why are we asking again. ๐
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.
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 a vibe.
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. Respect the boundaries bestie. ๐ซถ
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 as well as its C++ counterpart, 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. ๐ญ
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 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 bestie, this one is so sneaky. It finds initializations of C++ shared pointers to non-array type that are initialized with an array. Two different vibes colliding, and it's giving disaster.
If a shared pointer std::shared_ptr<T> is initialized with a new-expression new T[] the memory is not deallocated correctly. The pointer uses plain delete in this case to deallocate the target memory. Instead a delete[] call is needed. A std::shared_ptr<T[]> calls the correct delete operator. (the way that one little [] changes everything ๐ญ)
The check offers replacement of shared_ptr<T> to shared_ptr<T[]> if it is used at a single variable declaration (one variable in one statement).
Example, bestie, look at this:
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 However, only the std::shared_ptr case is detected by this check.
bugprone-signal-handler โก
Finds specific constructs in signal handler functions that can cause undefined behavior. ๐ The rules for what is allowed differ between C++ language versions. So like, it depends on the era you're living in, iykyk.
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. Periodt.
Asynchronous-safety is determined by comparing the function's name against a set of known functions. In addition, the function must come from a system header include and in a global namespace. The (possible) arguments passed to the function are not checked. Any function that cannot be determined to be asynchronous-safe is assumed to be non-asynchronous-safe by the check, including user functions for which only the declaration is visible. Calls to user-defined functions with visible definitions are checked recursively. ๐
This check implements the CERT C Coding Standard rule SIG30-C. Call only asynchronous-safe functions within signal handlers and the rule โ 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โ Selects which set of functions is considered 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 link for 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
The function quick_exit is not included in the POSIX list but it is included here in the set of safe functions. ๐
The default value is POSIX.
bugprone-signed-char-misuse ๐
cert-str34-c redirects here as an alias for this check. For the CERT alias, the DiagnoseSignedUnsignedCharComparisons option is set to false.
Finds those signed char -> integer conversions which might indicate a programming error. The basic problem with the signed char, that it might store the non-ASCII characters as negative values. This behavior can cause a misunderstanding of the written code both when an explicit and when an implicit conversion happens. ๐ซ
When the code contains an explicit signed char -> integer conversion, the human programmer probably expects that the converted value matches with the character code (a value from [0..255]), however, the actual value is in [-128..127] interval. To avoid 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 which matches the known character codes. โจ
In case of implicit conversion, the programmer might not actually be aware that a conversion happened and char value is used as an integer. There are some use cases when this unawareness might lead to a 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 this comparison, the two variables are converted to integers which have different value ranges. For signed char, the non-ASCII characters are stored as a value in [-128..-1] interval, while the same characters are stored in the [128..255] interval for an unsigned char.
It depends on the actual platform whether plain char is handled as signed char by default and so it is 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 char is assigned to an integer variable
- signed char and unsigned char are compared with equality/inequality operator
- signed char is converted to an integer in the array subscript
See also: STR34-C. Cast characters to unsigned char before converting to larger integer sizes
A good example from the CERT description 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.
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 will have the same type.
bool compare(signed char SChar, unsigned char USChar) {
if (static_cast<unsigned char>(SChar) == USChar)
return true;
return false;
}
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 this case, human misinterpretation is not an issue.DiagnoseSignedUnsignedCharComparisonsโ Whentrue, the check will warn on signed char/unsigned char comparisons, otherwise these comparisons are ignored. By default, this option is set totrue.
bugprone-sizeof-container
okay so this check catches you doing sizeof on STL container expressions. bestie. nine times out of ten you wanted .size(), not a byte count. ๐
Every class/struct type declared in namespace std:: that has a const size() method counts as a container here โ with two iconique exceptions: std::bitset and std::array. they're built different, they get a pass. โจ
Examples, and yes these are the real ones:
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 one finds usages of sizeof expressions that are, like, almost certainly errors. not a vibe. ๐ญ
quick refresher because it matters: 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. so yeah, it's giving security issue.
Suspicious usage of 'sizeof(K)'
the classic. you ask for the sizeof of an integer literal. that's literally just the size of its type (probably int). what you meant was probably to get the integer, not its size. ๐
#define BUFLEN 42
char buf[BUFLEN];
memset(buf, 0, sizeof(BUFLEN)); // sizeof(42) ==> sizeof(int)
Suspicious usage of 'sizeof(expr)'
sometimes you've got an enum or integer that represents a type, and the oopsie is querying sizeof on the integer or enum that represents the type that should be used by sizeof. you get the size of the integer, not of the type the integer represents. truly not the same thing. ๐
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. so sizeof(this) is handing you 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[]. so using sizeof on an object declared with char* type returns the size of a pointer instead of the number of characters (bytes) in the string literal. sneaky. ๐ฆ
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. no cap.
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 how you 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 /. bestie that's the whole bug right there.
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. macros stay doing the most. ๐
#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. ๐ซถ
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. that is so not it. ๐ญ
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.
Limitations
cases where the pointee type has a size of 1 byte (such as, and most importantly, char) are excluded. ok so char gets a hall pass. fine. ๐
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โ whentrue, the check will warn on pointer arithmetic where the element count is obtained from a division withsizeof(...), e.g.,Ptr + Bytes / sizeof(*T). Default istrue. ๐
๐ฆ bugprone-spuriously-wake-up-functions ๐ฆ
Okay bestie so this one is hunting for calls to cnd_wait, cnd_timedwait, wait, wait_for, or wait_until when the function is not being called from inside a loop that checks whether a condition predicate holds โ or when the function doesn't take a condition parameter at all. Spurious wakeups are real, and they will ruin your whole day. ๐
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. and the CERT C Coding Standard rule CON36-C. Wrap functions that can spuriously wake up in a loop.
๐ bugprone-standalone-empty ๐
Warns you when empty() gets called on a range and the result just... evaporates into the void. And if clear() is a real member function, it'll suggest that instead. โจ
The empty() method on several common ranges returns a Boolean telling you whether or not the range is empty, but people constantly misread it as "hey go ahead and clear this range for me." Some ranges do offer a clear() method for that exact purpose. So this check warns when a call to empty returns a result that's being ignored, and suggests replacing it with a call to clear() if clear() is available as a member function of the range.
Like, this code looks like it's saying something about emptiness, but the result is just thrown away:
std::vector<int> v;
...
v.empty();
Whereas a call to clear() would actually do the thing you probably meant:
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 (for example 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. Not the fun kind of suspicious. The "your build is cursed" kind.
A common mistake is to swap 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. Not a vibe.
Examples:
std::string("test", 200); // Will include random characters after "test".
std::string_view("test", 200);
Creating an empty string from constructors with parameters is considered suspicious. The programmer should use the empty constructor instead.
Examples:
std::string("test", 0); // Creation of an empty string.
std::string_view("test", 0);
โจ Options โจ
WarnOnLargeLengthโ When true, 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 to e.g. ::std::basic_string;llvm::StringRef;QString to perform this check on custom classes.
๐ธ bugprone-string-integer-assignment ๐ธ
This check finds assignments of an integer to std::basic_string<CharT> (std::string, std::wstring, etc.). The source of the problem is the following assignment operator of std::basic_string<CharT>:
basic_string& operator=( CharT ch );
Numeric types can be implicitly casted to character types. Which is chaos, frankly.
std::string s;
int x = 5965;
s = 6;
s = x;
Use the appropriate conversion functions or character literals. Like a civilized person. ๐ซถ
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. Sneaky little bug.
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 are keeping track of the bytes and the length. This is not the case for a char* (NUL-terminated) string. She does not keep track of anything, she just stops at the first NUL, periodt.
A common mistake is to pass a string-literal with embedded NUL to a string constructor expecting a NUL-terminated string. The bytes after the first NUL character are truncated. Just gone. ๐ญ
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 various ways that 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, braced initializer list does not compile so instead a call to .empty() or the empty string literal are 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 is 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). Not every enum wants to be a bitmask, bestie. Let her live.
- When "ADD" or "bitwise OR" is used between two enum which come from different types and these types value ranges are not disjoint.
The following cases will be investigated only using 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. ODR violations are the final boss of "why is my program doing that."
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 ๐ฅ
Finds potentially incorrect calls to memcmp() based on properties of the arguments. The following cases are covered:
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 and 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 and EXP62-CPP. Do not access the bits of an object representation that are not part of the object's value representation
cert-exp42-c redirects here as an alias of this check.
๐ง bugprone-suspicious-memset-usage ๐ง
This check finds memset() calls with potential mistakes in their arguments. Considering the function as void* memset(void* destination, int fill_value, size_t byte_count), the following cases are covered:
Case 1: Fill value is a character '0'
Filling up a memory area with ASCII code 48 characters is not customary, bestie โ like, possibly integer zeroes were intended instead? ๐ญ The check offers a replacement of '0' with 0. Memsetting character pointers with '0' is allowed though, so don't panic. ๐ซถ
Case 2: Fill value is truncated ๐
Memset converts fill_value to unsigned char before using it. If fill_value is out of unsigned character range, it gets truncated and memory will not contain the desired pattern. It's giving "why is my buffer not what I asked for". ๐ญ
Case 3: Byte count is zero โจ
Calling memset with a literal zero in its byte_count argument is likely to be unintended and swapped with fill_value. The check offers to swap these two arguments. Iconic little save. ๐
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 ๐
Okay so here's the lore: string literals placed side-by-side get concatenated at translation phase 6 (after the preprocessor). That's literally the whole point of the feature โ it's how you 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";
But the common mistake โ the one that gets everybody โ is programmers forgetting 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]). And the wild part? Clang won't generate warnings at compile time. Not a single one. ๐ So you just... live with it. ๐ฆ
This check may warn incorrectly on cases like:
const char* SupportedFormat[] = {
"Error %s",
"Code " PRIu64, // May warn here.
"Warning %s",
};
โจ Options โจ
SizeThresholdโ anunsigned integerspecifying the minimum size of a string literal to be considered by the check. Default is5U.RatioThresholdโ astringspecifying the maximum threshold ratio[0, 1.0]of suspicious string literals to be considered. Default is".2".MaxConcatenatedTokensโ anunsigned integerspecifying 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); โ and bestie, the problem with this construct is real: 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. So in either case, p = realloc(p, size); indicates bad coding style and can be replaced by q = realloc(p, size);. Slay, problem solved. ๐
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. No cap.
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. The vibe check, but for punctuation. ๐
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. Main character energy. ๐โจ
bugprone-suspicious-string-compare ๐ฆ
Find suspicious usage of runtime string comparison functions. This check is valid in C and C++. Ngl it's a lifesaver. ๐ซถ
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โ astringspecifying 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's giving "bestie why is this buffer longer than I thought". ๐
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. ๐ญ
The absence of a c_str() method in std::string_view often leads developers to use data() as a substitute, especially when interfacing with C APIs that expect null-terminated strings. However, since data() does not guarantee null termination, this can result in unintended behavior if the API relies on proper null termination for correct string interpretation. ๐ธ
In today's programming landscape, this scenario can occur when implicitly converting an std::string_view to an std::string. Since the constructor in std::string designed for string-view-like objects is explicit, attempting to pass an std::string_view to a function expecting an std::string will result in a compilation error. As a workaround, developers may be tempted to utilize the .data() method to achieve compilation, introducing potential risks. ๐ฆ
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 due to the explicit nature of the std::string constructor. Consequently, developers might opt for sv.data() to resolve the compilation error, albeit introducing potential hazards as discussed. ๐
StringViewTypesโ option allows users to specify custom string view-like types for analysis. It accepts a semicolon-separated list of type names or regular expressions matching these types. Default value is:::std::basic_string_view;::llvm::StringRef.AllowedCalleesโ specifies methods, functions, or classes where the result of.data()is passed to. Allows to 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 arguments that might've been swapped, by peeking at the implicit conversions going on. It looks at the types of the args being handed to a function and compares them against the types the params are supposed to be. Mismatch? Implicit conversion that smells like a swap? Warning time, bestie. ๐
void printNumbers(int a, float b);
int main() {
// Swapped arguments: float passed as int, int as float)
printNumbers(10.0f, 5);
return 0;
}
It covers a huge 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
Okay so important thing to note ๐ซถ โ for most argument swaps, the types need to match exactly. But there are exceptions, because of course there are. Specifically, when the swapped argument is of integral type, an exact match isn't always needed. Implicit casts from other integral types get the green light too. Same energy for floating-point args โ implicit casts between different floating-point types are considered acceptable. No cap.
And to avoid confusion, swaps where both swapped arguments are integral types or both are 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 it stays quiet. This approach prevents false positives and gives you flexibility when varying integral or floating-point types are being used on purpose. Iconic behaviour honestly.
bugprone-switch-missing-default-caseโ makes sureswitchstatements withoutdefaultcases get flagged. It only focuses on covering cases with non-enums, where the compiler might not be issuing 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 keeps executing without any defined behavior or handling. It's giving chaos. ๐
This check helps identify switch statements that are missing a default case, so developers 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 doesn't 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 provide 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. The math isn't mathing. ๐ญ
A struct or a class counts as a tagged union if it has exactly one union data member and exactly one enum data member and any number of other data members that are neither unions or 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.
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 and not the enum constants themselves. This way the enum constants that are essentially just aliases of other enum constants don't get included in the final count. Slay, no double counting. โจ
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 it's value is one less than the total number of distinct values in the enum.
When the final count is adjusted based on this heuristic then a diagnostic note is emitted that shows which enum constant matched the criteria. So you get receipts. ๐งพ
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
When enabled, the check will also give a warning, when the number of tags is greater than the number of union data members. Turned all the way up fr fr. ๐ฅ
This option is disabled by default.
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โ detectsdo whileloops with a condition always evaluating tofalsethat have acontinuestatement, because thiscontinueterminates the loop effectively. Not the vibe the author intended, I promise. ๐
void f() {
do {
// some code
continue; // terminating continue
// some other code
} while(false);
bugprone-throw-keyword-missing ๐
okay so this one is here to catch a very specific oopsie: you made a temporary object but forgot to actually throw it. Here's the vibe โ if the object's type derives from (or literally is) a class with EXCEPTION, Exception or exception somewhere in its name, we're gonna assume bestie's whole intention was to yeet that thing. So no throw = warning. periodt.
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 ๐
this check is looking for for loops where the loop variable has a "too small" type โ meaning the type literally can't represent every value in the iteration range. it's giving tiny-variable-energy and it's a problem.
int main() {
long size = 294967296l;
for (short i = 0; i < size; ++i) {}
}
this for loop is an infinite loop ๐ญ because short can't represent all the values in the [0..size] interval. it just... never gets there.
in a real use case, size means a container's size, which depends on user input:
int doSomething(const std::vector& items) {
for (short i = 0; i < items.size(); ++i) {}
}
this algorithm works great for a small amount of objects, but will lead to freeze for a larger user input. not the slay you were going for.
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 to avoid overlaps with that warning. iykyk.
MagnitudeBitsUpperLimit โ the 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 is 16. for example, if the user sets this option to 31 (bits), then a 32-bit unsigned int is ignored by the check, however a 32-bit int is not (a 32-bit signed int has 31 magnitude bits).
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. she's thorough, okay??
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're gonna 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 that whole 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. buckle up ๐
๐จ 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.opt().has_value()) {
use(*foo.opt()); // unsafe: it is unclear whether `foo.opt()` 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 are treated as accessors. note that this is not guaranteed to be safe โ but it is widely used (safely) in practice, and so we have chosen to treat it as generally safe. 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 that 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
since function results are not assumed to be stable across calls, 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.opt(); 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. bestie, just do the check. ๐
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
the check is aware of aliases of optional types that are created via using declarations. for example:
using OptionalInt = std::optional<int>;
void f(OptionalInt opt) {
use(opt.value()); // unsafe: it is unclear whether `opt` has a value.
}
lambdas
the check does not currently report unsafe optional accesses in lambdas. a future version will expand the scope to lambdas, following the rules outlined above. it is best to follow the same principles when using optionals in lambdas. ๐
access with operator*() vs. value()
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? 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 very 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.
best of both worlds, no cap ๐ โจ
๐ธ bugprone-undefined-memory-manipulation ๐ธ
okay bestie so this one is literally catching you doing crimes with memory ๐
It finds calls of memory manipulation functions memset(), memcpy() and memmove() on non-TriviallyCopyable objects โ and babes, that's 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.
For example, when using memcpy to copy std::string, pointer data is being copied, and it can result in a double free issue. Not the cute kind of double. ๐ซ
#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. Like, you almost did it bestie, you were so close. ๐
โจ 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. so like, you're off the hook in those cases. ๐ซถ
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. The way that a = a can just ruin your whole object... iconic of it, honestly. ๐
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
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;
}
};
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-unique-ptr-array-mismatch ๐ฆ
Finds initializations of C++ unique pointers to non-array type that are initialized with an array. ๐ฏ
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:
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. However, only the std::unique_ptr case is detected by this check. ๐
๐ 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. - Rule
MSC33-C. Do not pass invalid data to the asctime() function.
cert-msc24-c and cert-msc33-c redirect here as aliases of this check. โจ
๐ 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. โ ๏ธ
๐ง 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]];
...
"
okay so the functions get matched with POSIX extended regular expressions ๐ โ and heads up, bestie, those regexes do not support negative (?! matches, so don't even try it, it's giving false hope.
The reason is optional and it's just there to spill extra tea about why the replacement is happening. If you don't give one, the default reason is literally just is marked as unsafe. ๐
If replacement is empty, then instead of suggesting a replacement, the text it should not be used gets shown. Iconic behaviour honestly.
So like, for example, the config ^original$, replacement, is deprecated; will produce this diagnostic message:
original(); // warning: function 'original' is deprecated; 'replacement' should be used instead.
::std::original(); // no-warning
original_function(); // no-warning
and here's the matching lore, pay attention ๐: if your regular expression contains the character :, it gets matched against the qualified name (i.e. std::original). Otherwise the regex is matched against the unqualified name (original). And if the regex starts with :: (or ^::), it's matched against the fully qualified name (::std::original). Slay. ๐
โจ Options โจ
ReportMoreUnsafeFunctionsโ when it'strue, extra functions from widely used APIs (POSIX and friends ๐ซถ) get added to the list of reported functions. Check the main documentation of the check for the complete list of what this option actually turns on. Default istrue.ReportDefaultFunctionsโ when it'strue, the check reports the default set of functions. Switch it tofalseif you only wanna 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 info, 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 inside a function. ๐ฃ And bestie, the following types of variables are excluded from this check, no cap:
- 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
You can configure this check to warn on all non-trivial variables by setting IncludeTypes to .*, and then excluding specific types with ExcludeTypes. ๐
In this example, my_lock would generate a warning that it's unused:
std::mutex my_lock;
// my_lock local variable is never used
In the next example, future2 would generate a warning that it's 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's 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();
}
And here's the tragedy, bestie ๐ญ: the destructor of the scoped_lock gets called before the critical_section is entered, leaving it unprotected. It's giving false sense of security.
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're 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.
โจ Options โจ
CheckedFunctionsโ semicolon-separated list of functions to check. This parameter supports regexp. The function is checked if the name and scope matches, 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 that 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 that the information 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 the following 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 to void is 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. bestie this is THE classic, for example:
std::string str = "Hello, world!\n";
std::vector<std::string> messages;
messages.emplace_back(std::move(str));
std::cout << str;
That 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, a use, and a 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.) ๐
Okay so there's a catch bestie ๐ญ โ sometimes the check literally cannot tell that two branches are mutually exclusive. Like here (and yes, we're assuming 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. Formally bestie: the analysis is flow-sensitive but not path-sensitive. It's giving "close enough" energy and honestly? we forgive it ๐
Silencing erroneous warnings ๐ซถ
You can shut a wrong warning up by reinitializing the object after the move:
if (i == 1) {
messages.emplace_back(std::move(str));
str = "";
}
if (i == 2) {
std::cout << str;
}
And if you wanna dodge the overhead of actually reinitializing the object? Make a lil dummy function that tricks the check into assuming the object got reinitialized:
template <class T>
void IS_INITIALIZED(T&) {}
Use it like this:
if (i == 1) {
messages.emplace_back(std::move(str));
}
if (i == 2) {
IS_INITIALIZED(str);
std::cout << str;
}
No warning here because passing the object to a function as a non-const pointer or reference counts as a reinitialization โ see the Reinitialization section below, iykyk ๐ธ
Unsequenced moves, uses, and reinitializations ๐
C++ is so unserious sometimes: in many cases it makes zero guarantees about the order in which sub-expressions of a statement get evaluated. So in code like this, nobody can promise whether the use happens 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 that kind of situation, the check will note that the use and move are unsequenced. Noted. Logged. Observed. ๐
The check also respects sequencing rules when reinitializations show up in the same statement as moves or uses. A reinitialization only counts as reinitializing a variable if it's guaranteed to be evaluated after the move and before the use. No vibes-based guessing here, periodt.
Move ๐
Right now the check only looks at calls of std::move on local variables or function parameters. It does not check moves of member variables or global variables. Not on the menu bestie โ
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. Yeah. That's the rule ๐
Which means the check will flag a use-after-move even on a type that does not define a move constructor or move assignment operator. And that's 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 doesn't implement move semantics yet.
Also: if the result of std::move is passed to an rvalue reference parameter, that always counts as causing a move โ even if the function that consumes this parameter doesn't move from it, or only does so conditionally. Like, watch this:
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 doesn't. Again: intentional. We said what we said ๐
One special case though ๐ โ a call to std::move inside a try_emplace call is conservatively assumed not to move. This avoids spurious warnings, since the check has no way to reason about the bool returned by try_emplace. It simply cannot read that bool's mind and we respect that ๐
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 this code also triggers 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. One warning, one moment of shame, moving on ๐
Reinitialization โป๏ธ
The check considers a variable to be reinitialized in these 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.
If the variable in question is a struct and an individual member variable of that struct is written to, the check does not consider this to be a reinitialization โ even if, eventually, all member variables of the struct are written to. For example:
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 the last line; instead, the line that assigns to s.str will be flagged as a use-after-move. This is intentional as this pattern of reinitializing a struct is error-prone. For example, if an additional member variable is added to S, it is easy to forget to add the reinitialization for this additional member. Instead, it is safer to assign to the entire struct in one go, and this will also avoid the use-after-move warning. Slay responsibly ๐
bugprone-virtual-near-miss ๐ซฃ
Warn if a function is a near miss โ like, the name is very similar and the function signature is the same โ to a virtual function from a base class. It's giving "you almost had it bestie" energy.
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 ๐
The cert-arr39-c check is an alias โ please see bugprone-sizeof-expression for more information.
cert-con36-c ๐
The cert-con36-c check is an alias โ please see bugprone-spuriously-wake-up-functions for more information.
cert-con54-cpp ๐
The cert-con54-cpp check is an alias โ please see bugprone-spuriously-wake-up-functions for more information.
cert-ctr56-cpp ๐
The cert-ctr56-cpp check is an alias โ please see bugprone-pointer-arithmetic-on-polymorphic-object for more information.
cert-dcl03-c ๐
The cert-dcl03-c check is an alias โ please see misc-static-assert for more information.
cert-dcl16-c ๐
The cert-dcl16-c check is an alias โ please see readability-uppercase-literal-suffix for more information.
cert-dcl37-c ๐
The cert-dcl37-c check is an alias โ please see bugprone-reserved-identifier for more information.
cert-dcl50-cpp ๐ฉ
This check flags all function definitions (but not declarations) of C-style variadic functions. Yes, the ... ones. We see you ๐
This check corresponds to the CERT C++ Coding Standard rule DCL50-CPP. Do not define a C-style variadic function.
cert-dcl51-cpp ๐
The cert-dcl51-cpp check is an alias โ please see bugprone-reserved-identifier for more information.
cert-dcl54-cpp ๐
The cert-dcl54-cpp check is an alias โ please see misc-new-delete-overloads for more information.
cert-dcl58-cpp ๐
Modification of the std or posix namespace can result in undefined behavior. This check warns for such modifications. The std (or posix) namespace is allowed to be extended with (class or function) template specializations that depend on a user-defined type โ that's a type that is not defined in the standard system headers.
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 does not have any user-defined type as 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 bestie, buckle up:
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;
}
}
Do not poke the std namespace unless it's a user-defined-type specialization, bestie. Undefined behavior is not the cute kind of chaos ๐ญ
This check corresponds to the CERT C++ Coding Standard rule DCL58-CPP. Do not modify the standard namespaces.
cert-dcl59-cpp
Okay so cert-dcl59-cpp is literally just an alias, bestie โ go read google-build-namespaces for the actual tea. ๐
cert-env33-c
This one flags calls to system(), popen(), and _popen() โ aka the girlies that go spin up a whole command processor. ๐ซ
Heads up though: it does not flag calls to system() with a null pointer argument, because that kind of call is just checking whether a command processor even exists โ it's not actually trying to run a command. No cap.
This check corresponds to the CERT C Coding Standard rule ENV33-C. Do not call system().
cert-err09-cpp
Another alias, purr. cert-err09-cpp points you at misc-throw-by-value-catch-by-reference. ๐
This check corresponds to the CERT C++ Coding Standard recommendation ERR09-CPP. Throw anonymous temporaries. But โ plot twist โ all of the CERT recommendations got yeeted from public view, so seeing their justification for this check's behavior means you need an account on their wiki. ๐ญ
cert-err33-c
Bestie, this one is the return value police. ๐จ It warns on unused function return values. So many standard library functions hand you back a value telling you whether the call actually worked, and if you just ignore it, things can go sideways when an error happened. The following functions get 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()
This check is an alias of bugprone-unused-return-value, just with a fixed set of functions. โจ
Silencing issues by casting to void is on by default, and you can turn it off by setting the AllowCastToVoid option to false.
This check corresponds to a part of CERT C Coding Standard rule ERR33-C. Detect and handle standard library errors. The list of checked functions comes straight from that rule, with this one little exception:
- The check can't tell whether a function was called with a
NULLargument. So these functions are not checked:mblen,mbrlen,mbrtowc,mbtowc,wctomb,wctomb_s
cert-err34-c
This check flags calls to string-to-number conversion functions that don't verify whether the conversion was even valid โ think atoi() or scanf(). ๐ซฃ It does not flag calls to strtol() or other related conversion functions that do actual, proper error checking.
#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.
cert-err52-cpp
This one flags all call expressions involving setjmp() and longjmp(). Every last one. ๐ซ
This check corresponds to the CERT C++ Coding Standard rule ERR52-CPP. Do not use setjmp() or longjmp().
cert-err58-cpp
This check flags all static or thread_local variable declarations where the initializer for the object might throw an exception. ๐ฑ
This check corresponds to the CERT C++ Coding Standard rule ERR58-CPP. Handle all exceptions thrown before main() begins executing.
cert-err60-cpp
This check flags all throw expressions where the exception object is not nothrow copy constructible. ๐
This check corresponds to the CERT C++ Coding Standard rule ERR60-CPP. Exception objects must be nothrow copy constructible.
cert-err61-cpp
Alias alert ๐ โ cert-err61-cpp is just a redirect, so go see misc-throw-by-value-catch-by-reference for the details.
cert-exp42-c
Alias! cert-exp42-c points you at bugprone-suspicious-memory-comparison. ๐
cert-fio38-c
Another alias, bestie โ cert-fio38-c sends you to misc-non-copyable-objects. ๐ธ
cert-flp30-c
This check flags for loops where the induction expression has a floating-point type. Floats as loop counters? Not on this check's watch. ๐
โโ๏ธ
This check corresponds to the CERT C Coding Standard rule FLP30-C. Do not use floating-point variables as loop counters.
cert-flp37-c
Alias vibes โจ โ cert-flp37-c is an alias, please see bugprone-suspicious-memory-comparison.
cert-int09-c
Alias city ๐ โ cert-int09-c is an alias, please see readability-enum-initial-value.
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 give you the correct alignment if the requested alignment is less than or equal to the fundamental alignment. That's the tea. โ)
By design, only the cases where the operator new is not user-defined and is not a placement new get detected โ because in those cases we assume the user provided the correct memory allocation. ๐ซถ
This check corresponds to the CERT C++ Coding Standard rule MEM57-CPP. Avoid using default operator new for over-aligned types.
cert-msc24-c
Alias, babe โ cert-msc24-c is an alias, please see bugprone-unsafe-functions. ๐ฅ
cert-msc30-c
Alias! cert-msc30-c is an alias, please see cert-msc50-cpp. ๐
cert-msc32-c
Alias again ๐ โ cert-msc32-c is an alias, please see cert-msc51-cpp.
cert-msc33-c
You guessed it โ alias! cert-msc33-c is an alias, please see bugprone-unsafe-functions. โจ
cert-msc50-cpp
So pseudorandom number generators use mathematical algorithms to churn out a sequence of numbers with good statistical properties โ but bestie, those numbers are not genuinely random. ๐
The std::rand() function takes a seed (a number), runs a mathematical operation on it, and returns the result. Which means if you mess with the seed, the result becomes predictable. This check warns you about using std::rand().
cert-msc51-cpp
This check flags all pseudo-random number engines, engine adaptor instantiations and srand() when they're 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 โ like, say, in security protocols. This is a CERT security rule, so take it seriously: see MSC51-CPP. Ensure your random number generator is properly seeded and 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 values aretime_t,std::time_t.
cert-msc54-cpp
Alias energy ๐ โ cert-msc54-cpp is an alias, please see bugprone-signal-handler.
cert-oop11-cpp
Last one for this chunk, and yeah โ it's an alias too ๐ญ. cert-oop11-cpp is an alias, please see performance-move-constructor-init. ๐ฆ
The way that this check is literally the CERT C++ Coding Standard recommendation OOP11-CPP. Do not copy-initialize members or base classes from a move constructor. Okay but here's the tea โ โ 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-oop54-cpp ๐
So cert-oop54-cpp is an alias bestie, 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. it's giving "why are you doing that" energy fr fr.
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. periodt. ๐
cert-oop58-cpp ๐ธ
Finds assignments to the copied object and its direct or indirect members in copy constructors and copy assignment operators. not me finding your bugs bestie ๐ซถ
This check corresponds to the CERT C Coding Standard rule OOP58-CPP. Copy operations must not mutate the source object. no cap.
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. โจ
okay bestie, now we're entering the clang-analyzer era and it's a whole vibe. every single one of these is an alias pointing at the Clang Static Analyzer checkers, so let's get into it ๐
clang-analyzer-core.BitwiseShift ๐ง
Finds cases where bitwise shift operation causes undefined behaviour. ๐ (that's the UB warning bestie, pay attention)
The clang-analyzer-core.BitwiseShift check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.CallAndMessage ๐
Check for logical errors for function calls and Objective-C message expressions (e.g., uninitialized arguments, null function pointers).
The clang-analyzer-core.CallAndMessage check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.DivideZero ๐ฅ
Check for division by zero. iconic behaviour honestly (derogatory).
The clang-analyzer-core.DivideZero check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.NonNullParamChecker ๐
Check for null pointers passed as arguments to a function whose arguments are references or marked with the 'nonnull' attribute.
The clang-analyzer-core.NonNullParamChecker check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.NullDereference ๐ญ
Check for dereferences of null pointers. bestie NO ๐
The clang-analyzer-core.NullDereference check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.StackAddressEscape ๐ฆ
Check that addresses to stack memory do not escape the function. keep that address at home fr fr.
The clang-analyzer-core.StackAddressEscape check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.UndefinedBinaryOperatorResult ๐ธ
Check for undefined results of binary operators.
The clang-analyzer-core.UndefinedBinaryOperatorResult check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.VLASize ๐
Check for declarations of VLA of undefined or zero size.
The clang-analyzer-core.VLASize check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.uninitialized.ArraySubscript โจ
Check for uninitialized values used as array subscripts.
The clang-analyzer-core.uninitialized.ArraySubscript check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.uninitialized.Assign ๐
Check for assigning uninitialized values.
The clang-analyzer-core.uninitialized.Assign check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.uninitialized.Branch โญ
Check for uninitialized values used as branch conditions.
The clang-analyzer-core.uninitialized.Branch check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.uninitialized.CapturedBlockVariable ๐ซถ
Check for blocks that capture uninitialized values.
The clang-analyzer-core.uninitialized.CapturedBlockVariable check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.uninitialized.NewArraySize ๐
Check if the size of the array in a new[] expression is undefined.
The clang-analyzer-core.uninitialized.NewArraySize check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-core.uninitialized.UndefReturn ๐
Check for uninitialized values being returned to the caller.
The clang-analyzer-core.uninitialized.UndefReturn check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.ArrayDelete ๐
Reports destructions of arrays of polymorphic objects that are destructed as their base class. it's giving slicing but for your whole array ๐ญ
The clang-analyzer-cplusplus.ArrayDelete check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.InnerPointer ๐ง
Check for inner pointers of C++ containers used after re/deallocation.
The clang-analyzer-cplusplus.InnerPointer check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.Move ๐ฅ
Find use-after-move bugs in C++. you moved it bestie, you can't use it anymore, that's the whole deal ๐
The clang-analyzer-cplusplus.Move check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.NewDelete ๐
Check for double-free and use-after-free problems. Traces memory managed by new/delete.
The clang-analyzer-cplusplus.NewDelete check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.NewDeleteLeaks ๐
Check for memory leaks. Traces memory managed by new/delete.
The clang-analyzer-cplusplus.NewDeleteLeaks check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.PlacementNew ๐ธ
Check if default placement new is provided with pointers to sufficient storage capacity.
The clang-analyzer-cplusplus.PlacementNew check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.SelfAssignment ๐
Checks C++ copy and move assignment operators for self assignment. a = a bestie?? be so for real ๐ญ
The clang-analyzer-cplusplus.SelfAssignment check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-cplusplus.StringChecker ๐ฆ
Checks C++ std::string bugs.
The clang-analyzer-cplusplus.StringChecker check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-deadcode.DeadStores โจ
Check for values stored to variables that are never read afterwards.
The clang-analyzer-deadcode.DeadStores check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-fuchsia.HandleChecker ๐
A Checker that detect leaks related to Fuchsia handles.
The clang-analyzer-fuchsia.HandleChecker check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-nullability.NullPassedToNonnull โญ
Warns when a null pointer is passed to a pointer which has a _Nonnull type.
The clang-analyzer-nullability.NullPassedToNonnull check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-nullability.NullReturnedFromNonnull ๐
Warns when a null pointer is returned from a function that has _Nonnull return type.
The clang-analyzer-nullability.NullReturnedFromNonnull check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-nullability.NullableDereferenced ๐
Warns when a nullable pointer is dereferenced. bestie the pointer said it might be null and you dereferenced it anyway ๐ญ
The clang-analyzer-nullability.NullableDereferenced check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-nullability.NullablePassedToNonnull ๐ซถ
Warns when a nullable pointer is passed to a pointer which has a _Nonnull type.
The clang-analyzer-nullability.NullablePassedToNonnull check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-nullability.NullableReturnedFromNonnull ๐ง
Warns when a nullable pointer is returned from a function that has _Nonnull return type.
The clang-analyzer-nullability.NullableReturnedFromNonnull check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.core.EnumCastOutOfRange ๐ฅ
Check integer to enumeration casts for out of range values.
The clang-analyzer-optin.core.EnumCastOutOfRange check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.cplusplus.UninitializedObject ๐
Reports uninitialized fields after object construction.
The clang-analyzer-optin.cplusplus.UninitializedObject check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.cplusplus.VirtualCall ๐
Check virtual function calls during construction/destruction. the way that this is exactly the vibe of "you can't call virtuals in a constructor" and honestly it ate ๐
The clang-analyzer-optin.cplusplus.VirtualCall check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.mpi.MPI-Checker ๐ธ
Checks MPI code.
The clang-analyzer-optin.mpi.MPI-Checker check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker ๐
Check that NSLocalizedString macros include a comment for context.
quick lil alias alert incoming โ the clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker check is an alias, bestie, so pls see Clang Static Analyzer Available Checkers for the whole tea ๐
clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker
Warns you when you're out here passing non-localized NSStrings into UI methods that are literally expecting localized NSStrings. It's giving "why is this string not translated" energy ๐ญ
The clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.performance.GCDAntipattern
Checks for performance anti-patterns when you're using Grand Central Dispatch. Basically it's the vibe check for your GCD code ๐ฅ
The clang-analyzer-optin.performance.GCDAntipattern check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.performance.Padding
Checks for structs that are excessively padded โ all that wasted space, no cap ๐
The clang-analyzer-optin.performance.Padding check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.portability.UnixAPI
Finds implementation-defined behavior hiding inside your UNIX/Posix functions. Sneaky sneaky ๐
The clang-analyzer-optin.portability.UnixAPI check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-optin.taint.TaintedAlloc
Checks your memory allocations where the size parameter might be a tainted (aka attacker controlled) value. Red flag fr fr ๐ฉ
The clang-analyzer-optin.taint.TaintedAlloc check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.API
Checks that you're using various Apple APIs the proper way, no chaos allowed โจ
The clang-analyzer-osx.API check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.NumberObjectConversion
Checks for erroneous conversions of objects that represent numbers into actual numbers. The math is not mathing ๐ญ
The clang-analyzer-osx.NumberObjectConversion check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.ObjCProperty
Checks that you're using Objective-C properties properly ๐
The clang-analyzer-osx.ObjCProperty check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.SecKeychainAPI
Checks for proper uses of the Secure Keychain APIs ๐
The clang-analyzer-osx.SecKeychainAPI check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.AtSync
Checks for nil pointers being used as mutexes for @synchronized. Not the move bestie ๐
The clang-analyzer-osx.cocoa.AtSync check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.AutoreleaseWrite
Warns about potentially crashing writes to autoreleasing objects coming from different autoreleasing pools in Objective-C. It's giving danger ๐ญ
The clang-analyzer-osx.cocoa.AutoreleaseWrite check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.ClassRelease
Checks for when you send 'retain', 'release', or 'autorelease' directly to a Class. Rude. Not allowed โ
The clang-analyzer-osx.cocoa.ClassRelease check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.Dealloc
Warns about Objective-C classes that lack a correct implementation of -dealloc. Clean up after yourself periodt ๐งน
The clang-analyzer-osx.cocoa.Dealloc check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.IncompatibleMethodTypes
Warns about Objective-C method signatures with type incompatibilities. The types said "we're not compatible" ๐
The clang-analyzer-osx.cocoa.IncompatibleMethodTypes check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.Loops
Gives you improved modeling of loops that use Cocoa collection types. Slay, loops ๐
The clang-analyzer-osx.cocoa.Loops check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.MissingSuperCall
Warns about Objective-C methods that are missing a necessary call to super. Respect your elders fr ๐
The clang-analyzer-osx.cocoa.MissingSuperCall check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.NSAutoreleasePool
Warns about suboptimal uses of NSAutoreleasePool in Objective-C GC mode ๐ซ
The clang-analyzer-osx.cocoa.NSAutoreleasePool check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.NSError
Checks your usage of NSError** parameters. Those double stars are a whole situation โจ
The clang-analyzer-osx.cocoa.NSError check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.NilArg
Checks for prohibited nil arguments in ObjC method calls. nil is not always the vibe ๐
The clang-analyzer-osx.cocoa.NilArg check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.NonNilReturnValue
Models the APIs that are guaranteed to return a non-nil value. Iconic behavior honestly ๐
The clang-analyzer-osx.cocoa.NonNilReturnValue check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.ObjCGenerics
Checks for type errors when you're using Objective-C generics ๐
The clang-analyzer-osx.cocoa.ObjCGenerics check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.RetainCount
Checks for leaks and improper reference count management. Memory leaks are NOT the main character energy we want ๐ค
The clang-analyzer-osx.cocoa.RetainCount check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak
Checks for leaked memory in autorelease pools that will literally never be drained. Never?? NEVER. ๐ญ
The clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.SelfInit
Checks that 'self' is properly initialized inside an initializer method. Know yourself bestie ๐ช
The clang-analyzer-osx.cocoa.SelfInit check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.SuperDealloc
Warns about improper use of '[super dealloc]' in Objective-C ๐ซถ
The clang-analyzer-osx.cocoa.SuperDealloc check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.UnusedIvars
Warns about private ivars that are never used. They're just sitting there, unbothered, unemployed ๐
The clang-analyzer-osx.cocoa.UnusedIvars check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.cocoa.VariadicMethodTypes
Checks for when you pass non-Objective-C types into variadic collection initialization methods that only expect Objective-C types. Read the room, types ๐
The clang-analyzer-osx.cocoa.VariadicMethodTypes check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.coreFoundation.CFError
Checks your usage of CFErrorRef* parameters โจ
The clang-analyzer-osx.coreFoundation.CFError check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.coreFoundation.CFNumber
Checks for proper uses of CFNumber APIs ๐ข
The clang-analyzer-osx.coreFoundation.CFNumber check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.coreFoundation.CFRetainRelease
Checks for null arguments to CFRetain/CFRelease/CFMakeCollectable ๐จ
The clang-analyzer-osx.coreFoundation.CFRetainRelease check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.coreFoundation.containers.OutOfBounds
Checks for index out-of-bounds situations when you're using the 'CFArray' API. Stay in your lane, index ๐
The clang-analyzer-osx.coreFoundation.containers.OutOfBounds check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-osx.coreFoundation.containers.PointerSizedValues
Warns you if 'CFArray', 'CFDictionary', or 'CFSet' get created with non-pointer-size values ๐
The clang-analyzer-osx.coreFoundation.containers.PointerSizedValues check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.FloatLoopCounter
Warns on using a floating point value as a loop counter (CERT: FLP30-C, FLP30-CPP). Floats in loops? In this economy? ๐
The clang-analyzer-security.FloatLoopCounter check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.PutenvStackArray
Finds calls to the putenv function that pass a pointer to a stack-allocated (automatic) array as the argument. Here's the tea: putenv does not copy the passed string โ only a pointer to the data gets stored, and that data can be read even by other threads. The content of a stack-allocated array is likely to get overwritten after you exit the function. So yeah. Yikes ๐ญ
The clang-analyzer-security.PutenvStackArray check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.SetgidSetuidOrder
This checker looks for sequences of setuid(getuid()) and setgid(getgid()) calls (in this order). If it finds such a sequence and there's no other privilege-changing function call in between (seteuid, setreuid, setresuid and the GID versions of those), it generates a warning. And ngl, it's picky: the checker finds only exactly setuid(getuid()) calls (and the GID versions) โ so not, for example, if the result of getuid() is stored in a variable first ๐
The clang-analyzer-security.SetgidSetuidOrder check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.cert.env.InvalidPtr
Finds usages of possibly invalidated pointers. Oop, that pointer's expired ๐ซ
The clang-analyzer-security.cert.env.InvalidPtr check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling
Warns on uses of unsecure or deprecated buffer manipulating functions. Throw the whole function away ๐ฅ
The clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.insecureAPI.UncheckedReturn
Warns on uses of functions whose return values must always be checked. Like hello?? Check the return value bestie ๐ญ
The clang-analyzer-security.insecureAPI.UncheckedReturn check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.insecureAPI.bcmp
Warns on uses of the 'bcmp' function ๐ฉ
The clang-analyzer-security.insecureAPI.bcmp check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.insecureAPI.bcopy
Warns on uses of the 'bcopy' function ๐ฉ
The clang-analyzer-security.insecureAPI.bcopy check is an alias, please see Clang Static Analyzer Available Checkers for more information.
clang-analyzer-security.insecureAPI.bzero
Warns you any time someone rolls up using the bzero function. ๐ซ
And heads up bestie โ this check is an alias, so for the full scoop you gotta go peep Clang Static Analyzer Available Checkers. โจ
clang-analyzer-security.insecureAPI.decodeValueOfObjCType
Warns you whenever the -decodeValueOfObjCType:at: method shows up. ๐
This one is an alias of Clang Static Analyzer security.insecureAPI.decodeValueOfObjCType, okurrr?
clang-analyzer-security.insecureAPI.getpw
Warns you when getpw gets called. ๐ธ
Also an alias โ go check Clang Static Analyzer Available Checkers for the deets.
clang-analyzer-security.insecureAPI.gets
Warns you on any use of gets. ๐ซ
Another alias, bestie โ Clang Static Analyzer Available Checkers has the full story.
clang-analyzer-security.insecureAPI.mkstemp
Warns you when mkstemp gets handed a format string with fewer than 6 X's. ๐ญ
Yep, alias โ hit up Clang Static Analyzer Available Checkers for more.
clang-analyzer-security.insecureAPI.mktemp
Warns you whenever mktemp is used. ๐
Alias vibes โ see Clang Static Analyzer Available Checkers.
clang-analyzer-security.insecureAPI.rand
Warns you on uses of rand, random, and all their little friends. ๐ฒ
Alias, bestie โ Clang Static Analyzer Available Checkers is where it's at.
clang-analyzer-security.insecureAPI.strcpy
Warns you on uses of strcpy and strcat. ๐ฌ
Also an alias โ go see Clang Static Analyzer Available Checkers.
clang-analyzer-security.insecureAPI.vfork
Warns you when vfork gets used. ๐
Alias energy โ Clang Static Analyzer Available Checkers, babes.
clang-analyzer-unix.API
Checks the calls you make to a whole bunch of UNIX/Posix functions. ๐
This check is an alias โ please see Clang Static Analyzer Available Checkers for more.
clang-analyzer-unix.BlockInCriticalSection
Checks for calls to blocking functions happening inside a critical section. ๐ง
Alias โ see Clang Static Analyzer Available Checkers.
clang-analyzer-unix.Errno
Checks for improper use of errno. ๐ตโ๐ซ
Alias โ Clang Static Analyzer Available Checkers has you.
clang-analyzer-unix.Malloc
Checks for memory leaks, double free, and use-after-free problems. It traces memory managed by malloc()/free(). ๐ง
Alias โ see Clang Static Analyzer Available Checkers for more.
clang-analyzer-unix.MallocSizeof
Checks for dubious malloc arguments that involve sizeof. ๐คจ
Alias โ Clang Static Analyzer Available Checkers, bestie.
clang-analyzer-unix.MismatchedDeallocator
Checks for mismatched deallocators. ๐ซฃ
Alias โ see Clang Static Analyzer Available Checkers.
clang-analyzer-unix.StdCLibraryFunctions
Checks for invalid arguments passed to C standard library functions, and applies the relations between arguments and the return value. ๐
Alias โ Clang Static Analyzer Available Checkers for the details.
clang-analyzer-unix.Stream
Checks stream handling functions. ๐
Alias โ see Clang Static Analyzer Available Checkers.
clang-analyzer-unix.Vfork
Checks that you're using vfork properly. โ
Alias โ Clang Static Analyzer Available Checkers, babe.
clang-analyzer-unix.cstring.BadSizeArg
Checks the size argument you pass into C string functions for the usual erroneous patterns. ๐
Alias โ see Clang Static Analyzer Available Checkers.
clang-analyzer-unix.cstring.NullArg
Checks for null pointers being passed as arguments to C string functions. ๐ณ๏ธ
Alias โ please see Clang Static Analyzer Available Checkers for more.
clang-analyzer-webkit.NoUncountedMemberChecker
Checks for no uncounted member variables. ๐ง
Alias โ Clang Static Analyzer Available Checkers has the info.
clang-analyzer-webkit.RefCntblBaseVirtualDtor
Checks that any ref-countable base class has a virtual destructor. ๐
Alias โ see Clang Static Analyzer Available Checkers.
clang-analyzer-webkit.UncountedLambdaCapturesChecker
Checks uncounted lambda captures. ๐ฏ
Alias โ Clang Static Analyzer Available Checkers, bestie.
concurrency-mt-unsafe
Checks a black list of known-to-be-unsafe functions for thread-unsafe behavior. Usually these either touch static variables with zero synchronization (like gmtime(3)) or use signals in a racy way. ๐โโ๏ธ๐จ
Which functions get checked? That's what the FunctionSet option is for.
Ngl, using some thread-unsafe functions can still be valid in concurrent programming โ but only if a single thread is used (think setenv(3)). But some functions track state in global variables that would get clobbered by subsequent (non-parallel, but concurrent) calls to a related function. ๐ญ
Like, check this out โ this 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
FunctionSet
Specifies which functions in libc should be considered thread-safe. Possible values are posix, glibc, or any. ๐
posixmeans 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 the 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 you're just unsure which libc will be used โ use
any(default). ๐
concurrency-thread-canceltype-asynchronous
Finds pthread_setcanceltype function calls where a thread's cancellation type is set to asynchronous. ๐จ
Asynchronous cancellation type (PTHREAD_CANCEL_ASYNCHRONOUS) is generally unsafe โ use typePTHREAD_CANCEL_DEFERREDinstead, which is 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. ๐ต
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. ๐
cert-pos47-c redirects here as an alias of this check. ๐ช
cppcoreguidelines-avoid-c-arrays
The cppcoreguidelines-avoid-c-arrays check is an alias โ please see modernize-avoid-c-arrays for more information.
cppcoreguidelines-avoid-capturing-lambda-coroutines
Flags C++20 coroutine lambdas that have non-empty capture lists โ the kind that can cause use-after-free errors โ and suggests either avoiding captures or making sure the lambda closure object has a guaranteed lifetime. ๐
This check implements CP.51 from the C++ Core Guidelines. ๐
Using coroutine lambdas with non-empty capture lists can be risky, because capturing variables can lead to accessing freed memory after the first suspension point. This issue can happen 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 go out of scope too. Normal lambdas finish executing before that happens โ but coroutine lambdas may resume from suspension after the closure object has been destructed, resulting in use-after-free memory access for all the 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. ๐
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. ๐
cppcoreguidelines-avoid-const-or-ref-data-members
This check warns when structs or classes that are copyable or movable also have const-qualified or reference (lvalue or rvalue) data members. ๐
Having such members is rarely useful, and it makes the class only copy-constructible but not copy-assignable. ๐ซ
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 from the C++ Core Guidelines.
Further reading: Data members: Never const.
๐ cppcoreguidelines-avoid-do-while ๐
Warns when using do-while loops. Bestie, they're less readable than plain while loops, because the termination condition sits at the end and the condition isn't checked before that first iteration. That's lowkey how subtle bugs get born. ๐ญ
This check implements ES.75 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. Periodt.
This check implements ES.76 from the C++ Core Guidelines and 6.3.1 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..
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++. ๐
๐ฎ cppcoreguidelines-avoid-magic-numbers ๐ฎ
The cppcoreguidelines-avoid-magic-numbers check is an alias, please see readability-magic-numbers for more information.
๐ cppcoreguidelines-avoid-non-const-global-variables ๐
Finds non-const global variables as described in I.2 of C++ Core Guidelines. As R.6 of C++ Core Guidelines is a duplicate of rule I.2 it also covers that rule. Two-for-one, slay.
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. ๐ค
โจ Options โจ
AllowInternalLinkageโ when set totrue,staticnon-constvariables 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. Fr fr, don't do it.
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 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) profile and ES.20 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 specifying which include-style is used,llvmorgoogle. Default isllvm.MathHeaderโ a string specifying the header to include to get the definition ofNAN. Default is<math.h>.
๐ง cppcoreguidelines-interfaces-global-init ๐ง
This check flags initializers of globals that access extern objects, and therefore can lead to order-of-initialization problems. ๐
This check implements I.22 from the C++ Core Guidelines.
Note that currently this does not flag calls to non-constexpr functions, and therefore globals could still be accessed from functions themselves. ๐
โญ cppcoreguidelines-macro-to-enum โญ
The cppcoreguidelines-macro-to-enum check is an alias, please see modernize-macro-to-enum for more information.
๐ cppcoreguidelines-macro-usage ๐
Finds macro usage that is considered problematic because better language constructs exist for the task.
The relevant sections in the C++ Core Guidelines are ES.31, and ES.32.
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__)
^
โจ 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. Default value isfalse.IgnoreCommandLineMacrosโ Boolean flag to toggle ignoring command-line-defined macros. Default value istrue.
๐ cppcoreguidelines-misleading-capture-default-by-value ๐
Warns when lambda specify a by-value capture default and capture this.
By-value capture defaults in member functions can be misleading about whether data members are captured by value or reference. This occurs because specifying the capture default [=] actually captures the this pointer by value, not the data members themselves. As a result, data members are still indirectly accessed via the captured this pointer, which essentially means they are being accessed by reference. Therefore, even when using [=], data members are effectively captured by reference, which might not align with the user's expectations. ๐ญ the betrayal.
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.
๐ธ cppcoreguidelines-missing-std-forward ๐ธ
Warns when a forwarding reference parameter is not forwarded inside the function body. ๐
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
}
This check implements F.19 from the C++ Core Guidelines.
cppcoreguidelines-narrowing-conversions ๐
bestie this one is literally just implementing part of ES.46 from the C++ Core Guidelines โจ
and that's it, because it's an alias โ for the actual facts and tea, please go read bugprone-narrowing-conversions ๐ซถ
cppcoreguidelines-no-malloc ๐
okay so this check is all about C-Style memory management โ you know, the classics: malloc(), realloc(), calloc() and free() ๐
it warns you when you use them, and it tries to suggest that you use an appropriate RAII object instead. and get this โ you can also configure it to check against your own user-specified list of functions that are used for memory management (like posix_memalign(), for example) ๐
no cap, there is no attempt made to provide fix-it hints here, because manual resource management isn't easily transformed automatically into RAII. sorry bestie, you're doing this one by hand ๐
This check implements R.10 from the C++ Core Guidelines.
// 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 ๐
this check flags coroutines that suspend while a lock guard is in scope at the suspension point. it's giving "why is this mutex still locked" energy ๐ญ
here's the lore: when a coroutine suspends, any mutexes held by the coroutine stay locked until the coroutine resumes and eventually destructs the lock guard. that can lead to long periods with a mutex held, and it runs the risk of deadlock ๐
so instead: locks should be released before suspending a coroutine, periodt โจ
also important โ this check only checks suspending coroutines while a lock_guard is in scope. it does not consider manual locking or unlocking of mutexes, e.g. through calls to std::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.
cppcoreguidelines-noexcept-destructor โจ
This check implements C.37 from the C++ Core Guidelines.
and also, 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.
yep and the cppcoreguidelines-noexcept-move-operations check is an alias too โ please see performance-noexcept-move-constructor for more information ๐ธ
cppcoreguidelines-noexcept-swap ๐
This check implements C.83 , C.84 and C.85 from the C++ Core Guidelines.
and surprise, the cppcoreguidelines-noexcept-swap check is an alias as well โ 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 ๐ซถ
cppcoreguidelines-owning-memory ๐ฅ
okay this one is a big one, bestie, sit down. this check implements the type-based semantics of gsl::owner<T*>, which lets you do static analysis on code that uses raw pointers to handle resources like dynamic memory โ but won't introduce RAII concepts. it's like, the vibes of ownership without the whole lifestyle change ๐
This check implements I.11, C.33, R.3 and GSL.Views from the C++ Core Guidelines.
the definition of a gsl::owner<T*> is straight forward:
namespace gsl { template <typename T> owner = T; }
so it is therefore simple to introduce the owner even without using an implementation of the Guideline Support Library โจ
heads up: all checks are purely type based and not (yet) flow sensitive.
the following examples will demonstrate the correct and incorrect initializations of owners โ assignment is handled the same way. note that 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
in the case of dynamic memory as 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 argument get called with either a gsl::owner<T*> or a newly created resource ๐
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. yes really, bestie:
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 ๐ therefore using gsl::owner<T*> in a heavy templated code base might lead to false positives.
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 that it's only 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 check finds member initializations in the constructor body which can be converted into member initializers of the constructor instead โจ and it's not just a glow-up for readability, bestie โ it also positively affects its performance ๐ฅ
class-member assignments inside a control statement, or following the first control statement, are ignored ๐
This check implements C.49 from the C++ Core Guidelines.
please note, that this check does not enforce rule C.48 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;
}
};
okay so here's the situation: n can be initialized in the constructor initializer list, but m literally cannot, because 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;
}
};
same energy, different constructor: n can go in the constructor initializer list, m cannot, because 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.
Pointers should not be used as arrays, bestie. 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. ๐
๐ 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".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.
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 a const or a volatile qualifier.
Modifying a variable that has been declared as const in C++ is generally considered undefined behavior, and this remains true even when using const_cast. In C++, the const qualifier indicates that a variable is intended to be read-only, and the compiler enforces this by disallowing any attempts to change the value of that variable.
Removing the volatile qualifier in C++ can have serious consequences. This qualifier indicates that a variable's value can change unpredictably, and removing it may lead to undefined behavior, optimization problems, and debugging challenges. It's essential to retain the volatile qualifier in situations where the variable's volatility is a crucial aspect of program correctness and reliability. ๐ซก
This rule is part of the Type safety (Type 3) profile and ES.50: Don't cast away const rule from the C++ Core Guidelines.
Options
StrictModeโ when this setting is set totrue, it means that any usage ofconst_castis not allowed. On the other hand, when it's set tofalse, 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 to be accessed 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.
This rule is part of the Type safety (Type.4) profile 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 โ e.g. 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.
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.
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{};).
This rule is part of the Type safety (Type.6) profile 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 to be accessed as if it were of an unrelated type Z. ๐
This rule is part of the Type safety (Type.1.1) profile 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 to be accessed as if it were of an unrelated type Z.
This rule is part of the Type safety (Type.2) profile 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. ๐
This rule is part of the Type safety (Type.7) profile 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 the function is used in an unevaluated context.
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 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. ๐
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);
}
This check implements F.18 from the C++ Core Guidelines. ๐
cppcoreguidelines-slicing ๐ธ
Flags slicing of member variables or vtable. Slicing is what happens when you copy a derived object into a base object: the members of the derived object โ both member variables and virtual member functions โ get yeeted into the void. ๐ซ And this is so misleading, especially for member function slicing. Like, bestie, look:
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 and C.145 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.
Note that defining a function with = delete is considered to be a definition. ๐
This check implements C.21 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โ When set totrue(default isfalse), this check doesn't flag classes which define no move operations at all. It still flags classes which define only one of either move constructor or move assignment operator. With this option enabled, the following class won't be flagged:
struct A {
A(const A&);
A& operator=(const A&);
~A();
};
AllowMissingMoveFunctionsWhenCopyIsDeletedโ When set totrue(default isfalse), this check doesn't flag classes which define deleted copy operations but don't define move operations. This flag is related to Google C++ Style Guide โ Copyable and Movable Types. With this option enabled, the following class won't be flagged:
struct A {
A(const A&) = delete;
A& operator=(const A&) = delete;
~A();
};
AllowImplicitlyDeletedCopyOrMoveโ When set totrue(default isfalse), this check doesn't flag classes which implicitly delete copy or move operations. With this option enabled, the following class won't be flagged:
struct A : boost::noncopyable {
~A() { std::cout << "dtor\n"; }
};
cppcoreguidelines-use-default-member-init ๐
This check implements C.48 from the C++ Core Guidelines.
The cppcoreguidelines-use-default-member-init check is an alias, please see modernize-use-default-member-init for more information. iykyk ๐ซ
cppcoreguidelines-virtual-class-destructor ๐
Finds virtual classes whose destructor is neither public and virtual nor protected and non-virtual. A virtual class's destructor should be specified in one of these ways to prevent undefined behavior. fr fr, pick one.
This check implements C.35 from the C++ Core Guidelines.
Note that this check will diagnose a class with a virtual method regardless of whether the class is used as a base class or not. ๐
Fixes are available for user-declared and implicit destructors that are either public and non-virtual or protected and virtual. 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 and is thus left to the user. ๐
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 due to potential livelock problems. yeah it's giving deprecated energy, bestie.
This check will detect 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.
It is 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 is not your friend like that.
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 for more information.
fuchsia-default-arguments-calls ๐
Warns if a function or method is called with default arguments.
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.
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, 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:
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 a pass, iconic behavior.
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.
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 lambda with otherwise unutterable return types.
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 โจ
Okay so this one's checking that make_pair's template arguments are deduced โ like, let the compiler do its thing, bestie. ๐
Why do we care? Because G++ 4.6 in C++11 mode fails badly if make_pair's template arguments get spelled out explicitly ๐ญ, and honestly that's just not the intended use in any case.
Corresponding cpplint.py check name: build/explicit_make_pair.
google-build-namespaces ๐
Heads up: cert-dcl59-cpp redirects here as an alias for this check. Also fuchsia-header-anon-namespaces redirects here as an alias for this check. Two for the price of one, we love to see it. ๐ซถ
It finds anonymous namespaces in headers.
https://google.github.io/styleguide/cppguide.html#Namespaces
Corresponding cpplint.py check name: build/namespaces.
google-build-using-namespace ๐ฆ
Finds using namespace directives. Periodt.
This check implements the following rule from the Google C++ Style Guide: 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. That's it, that's the check.
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 โ because otherwise you're just out here risking unintentional implicit conversions, and that's giving chaos energy.
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, bestie. ๐ญ
The check will suggest inserting explicit before the constructor or conversion operator declaration. But (important!) copy and move constructors should not be explicit, and neither should constructors taking a single initializer_list argument.
So 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. (Growth mindset, ig. ๐)
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 โ both of which are prohibited by the Google Objective-C style guide.
Said guide forbids calling +new or overriding it in class implementations, and prefers +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.
google-objc-avoid-throwing-exception ๐ค
Finds uses of throwing exceptions 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 returning 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 don't follow the pattern described in the Google Objective-C Style Guide. Rude of them, honestly.
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 don't 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. Iconic behaviour, ngl. ๐
For code:
static NSString* myString = @"hello";
The fix will be:
static NSString* gMyString = @"hello";
Another example, of a constant:
static NSString* const myConstString = @"hello";
The fix will be:
static NSString* const kMyConstString = @"hello";
However โ for code that's 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. ๐ญ You got this tho.
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. (Justice for FRIEND_TEST ig.)
For example:
TEST(TestSuiteName, Illegal_TestName) {}
TEST(Illegal_TestSuiteName, TestName) {}
would trigger the check. Underscores are not allowed in test suite names nor test names.
The DISABLED_ prefix, which may be used to disable test suites and individual tests, is removed from the test suite name and test name before checking for underscores.
This check does not propose any fixes. Just vibes and judgement. ๐
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. Bestie, if you're gonna leave a TODO, sign it. ๐
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 suggests replacing them with u?intXX(_t)?. Glow up for your integer types, fr fr.
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. It's a whole rebrand, and honestly? The new name ate.
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. Very official. Very serious. ๐
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.
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.
hicpp-braces-around-statements ๐
The hicpp-braces-around-statements check is an alias, please see readability-braces-around-statements for more information. It enforces the rule 6.1.1.
hicpp-deprecated-headers ๐
bestie this one's an alias โ no original content here, just a redirect. go read modernize-deprecated-headers for the full story. it enforces rule 1.3.3. that's it, that's the check. โจ
hicpp-exception-baseclass ๐
okay so the vibe check here: every single value that shows up in a throw expression has to be an instance of std::exception. no random ints getting yeeted into the void, understood?
this enforces rule 15.1 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. it's used to enforce parts of rule 5.4.1. what it does: makes sure constructors and conversion operators are marked explicit. the other forms of casting checks live elsewhere, okay? if you wanna check for more forms of casting, these are your girls:
cppcoreguidelines-pro-type-static-cast-downcastcppcoreguidelines-pro-type-reinterpret-castcppcoreguidelines-pro-type-const-castcppcoreguidelines-pro-type-cstyle-cast
hicpp-function-size ๐ธ
alias for readability-function-size. super useful for enforcing multiple sections about function complexity. the rules it's serving:
- rule 8.2.2
- rule 8.3.1
- rule 8.3.2
hicpp-ignored-remove-result ๐ซถ
so the tea here: make sure the result of std::remove, std::remove_if and std::unique does not get ignored, per rule 17.5.1.
here's why, bestie: the mutating algorithms std::remove, std::remove_if and both overloads of std::unique do their thing by swapping or moving elements of the range they're 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. periodt.
also important: this check is a subset of bugprone-unused-return-value, so depending on the options you use, it can be superfluous to enable both checks. iykyk.
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. it implements parts of rule 8.4.1 to check if moved-from objects are being accessed. (rude. don't touch what's been moved from.)
hicpp-member-init ๐ฆ
alias for cppcoreguidelines-pro-type-member-init. it implements the check for rule 12.4.2 โ initialize class members in the right order, bestie, the order matters.
hicpp-move-const-arg ๐
the hicpp-move-const-arg check is an alias, go see performance-move-const-arg for more info. it enforces rule 17.3.1.
hicpp-multiway-paths-covered ๐ฅ
this one discovers situations where code paths are not fully covered. it also suggests using if instead of switch when the code would just be clearer that way. rule 6.1.2 and rule 6.1.4 of the High Integrity C++ Coding Standard are enforced here.
so if-else if chains that miss a final else branch? those might lead to unexpected program execution and be the result of a logical error. if the missing else branch is intentional, you can leave it empty with a clarifying comment. also heads up: this warning can be noisy on some code bases, so it's 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 that don't 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
}
and rule 6.1.4 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 get caught too, no cap.
// 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 ๐
alias for readability-named-parameter. it implements rule 8.2.1.
hicpp-new-delete-operators ๐
alias for misc-new-delete-overloads. it implements rule 12.3.1 to make sure the new and delete operators have the correct signature.
hicpp-no-array-decay โญ
the hicpp-no-array-decay check is an alias, go see cppcoreguidelines-pro-bounds-array-to-pointer-decay for more info. it enforces rule 4.1.1.
hicpp-no-assembler ๐ง
checks for assembler statements. use of inline assembly should be avoided since it restricts the portability of the code. (portability is the moment, bestie.)
this enforces rule 7.5.1 of the High Integrity C++ Coding Standard.
hicpp-no-malloc ๐
the hicpp-no-malloc check is an alias, go see cppcoreguidelines-no-malloc for more info. it enforces rule 5.3.2.
hicpp-noexcept-move โจ
alias for performance-noexcept-move-constructor. it checks rule 12.5.4 โ mark move assignment and move construction noexcept.
hicpp-signed-bitwise ๐
finds uses of bitwise operations on signed integer types, which may lead to undefined or implementation defined behavior. yeah, that's giving danger.
the according rule is defined in the High Integrity C++ Standard, Section 5.6.1.
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 ๐
alias for cppcoreguidelines-special-member-functions. it checks that special member functions have the correct signature, according to rule 12.5.7.
hicpp-static-assert ๐ธ
the hicpp-static-assert check is an alias, go see misc-static-assert for more info. it enforces rule 7.1.10.
hicpp-undelegated-constructor ๐ฆ
alias for bugprone-undelegated-constructor. it partially implements rule 12.4.5 to find misplaced constructor calls inside a constructor. (classic oops.)
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, go see readability-uppercase-literal-suffix for more info.
it partially implements rule 4.2.1 to ensure that the U suffix is writeln properly.
hicpp-use-auto ๐
the hicpp-use-auto check is an alias, go see modernize-use-auto for more info. it enforces rule 7.1.8.
hicpp-use-emplace ๐ฅ
the hicpp-use-emplace check is an alias, go see modernize-use-emplace for more info. it enforces rule 17.4.2.
hicpp-use-equals-default โจ
alias for modernize-use-equals-default. it implements rule 12.5.1 โ explicitly default special member functions.
hicpp-use-equals-delete ๐
alias for modernize-use-equals-delete. it implements rule 12.5.1 โ explicitly default or delete special member functions.
hicpp-use-noexcept ๐ธ
the hicpp-use-noexcept check is an alias, go see modernize-use-noexcept for more info. it enforces rule 1.3.5.
hicpp-use-nullptr โญ
the hicpp-use-nullptr check is an alias, go see modernize-use-nullptr for more info. it enforces rule 2.5.3.
hicpp-use-override ๐ซถ
alias for modernize-use-override. it implements rule 10.2.1 โ declare a virtual function override when overriding. say it with your chest.
hicpp-vararg ๐
the hicpp-vararg check is an alias, go see cppcoreguidelines-pro-type-vararg for more info. it enforces rule 14.1.1.
linuxkernel-must-check-errs ๐ง
this one checks Linux kernel code to see if it uses the results from the functions in linux/err.h. it also checks to see if code uses the results from functions that directly return a value from one of these error functions.
why does this matter, bestie? because in the Linux kernel, ERR_PTR, PTR_ERR, IS_ERR, IS_ERR_OR_NULL, ERR_CAST, and PTR_ERR_OR_ZERO return values must be checked โ since positive pointers and negative error codes are being used in the same context. these functions are marked with __attribute__((warn_unused_result)), but some kernel versions do not have this warning enabled for clang. so, yeah. check your errs. ๐
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, go see readability-else-after-return for more info.
llvm-header-guard ๐
finds and fixes header guards that do not adhere to LLVM style.
llvm-include-order ๐ฅ
checks the correct order of #includes.
see https://llvm.org/docs/CodingStandards.html#include-style
llvm-namespace-comment ๐
google-readability-namespace-comments redirects here as an alias for this check.
checks that long namespaces have a closing comment.
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โ bestie, this one wants the closing brace of a namespace definition to be followed by a closing comment if the namespace body has more thanShortNamespaceLineslines of code. It's an unsigned integer and it defaults to1U.SpacesBeforeCommentsโ an unsigned integer that says how many spaces go before that comment closing a namespace definition. Default?1U. periodt.
๐ฆ llvm-prefer-isa-or-dyn-cast-in-conditionals
okay so this one goes snooping around conditionals and finds + replaces cast<> (which will assert instead of returning a null pointer ๐ญ) and dyn_cast<> where nobody captured the return value. Plus it hunts down the pattern var && isa<X>(var) where var gets evaluated twice. no cap.
// 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 holding vregs and physregs, and rewrites them to use Register. so iconic.
Right now the way it works is: it finds every variable of unsigned integer type whose initializer starts 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-qualified-auto
heads up bestie, llvm-qualified-auto is just an alias โ please go see readability-qualified-auto for the actual info. ๐ซถ
๐ llvm-twine-local
this one is looking for local Twine variables, which are lowkey prone to use-after-frees and should generally be avoided. not the vibe.
static Twine Moo = Twine("bark") + "bah";
// becomes
static std::string Moo = (Twine("bark") + "bah").str();
โญ llvmlibc-callee-namespace
checks that all calls resolve to functions inside the correct namespace. slay.
// 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 every declaration in the llvm-libc implementation lives inside the correct namespace. fr fr.
// 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 every implicitly and explicitly inline function in header files is tagged with the LIBC_INLINE macro โ except for functions implicit to classes or deleted functions. go read the libc style guide for more about this macro, bestie. ๐
๐ฅ llvmlibc-restrict-system-libc-headers
finds includes of system libc headers that the compiler did not provide, inside llvm-libc implementations. caught in 4k.
#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.
๐ why does this check even exist? because accidentally including system libc headers can lead to subtle, hard-to-detect bugs. imagine a system libc whose dirent struct has a slightly different field ordering than llvm-libc. it'll compile just fine and then cause runtime issues, because they're ABI incompatible. the audacity.
โจ Options โจ
Includesโ a string with a comma separated glob list of allowed include filenames. similar to the-checksglob list for running clang-tidy itself: the two wildcard characters are*and-, to include and exclude globs, respectively. the default is-*, which disallows all includes.
this can be used to allow known safe includes, like Linux development headers. see portability-restrict-system-includes for more details. ๐ซถ
๐ misc-confusable-identifiers
warns you about confusable identifiers โ i.e. identifiers that look visually close to each other but use different Unicode characters. this detects a potential attack described in CVE-2021-42574. ๐ scary but iconic.
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 aren't. and declaring stuff const is required or recommended by so many coding guidelines, like: ES.25 from the C++ Core Guidelines. we love a good rule.
heads up tho: this check's analysis is type-based only. variables that aren't modified but are 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 โ but not (yet) 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 not (yet) analyzed.
int *pointer_variable = &potential_const_int; // _NO_ 'const int *pointer_variable' suggestion.
int last_copy = *pointer_variable;
the automatic code transformation only applies to variables declared in single declarations. you may want to prep your code base with readability-isolate-declaration first. ๐
also note there's the check cppcoreguidelines-avoid-non-const-global-variables to enforce const correctness on all globals. iykyk.
๐ 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's just not possible to find all instantiations of a template โ the template might be used differently in an independent translation unit. ๐ญ
pointees can't be analyzed for constness yet. this code right here shows the limitation:
// Declare a variable that will not be modified.
int constant_value = 42;
// Declare a pointer to that variable, that does not modify either, but misses 'const'.
// Could be 'const int *pointer_to_constant = &constant_value;'
int *pointer_to_constant = &constant_value;
// Usage:
int result = 520 * 120 * (*pointer_to_constant);
this limitation also means you can't add const to methods. yeah, that's not possible either. ๐
โจ Options โจ
AnalyzeValues(default =true) โ enable or disable the analysis of ordinary value variables, likeint i = 42;
// 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(default =true) โ enable or disable the analysis of reference variables, likeint &ref = i;
int i = 42;
// Warning
int& ref = i;
// No warning
int const& ref = i;
WarnPointersAsValues(default =false) โ this option enables the suggestion forconstof the pointer itself. pointer values have two possibilities to be const: the pointer, and the value pointing to.
int value = 42;
// Warning
const int * pointer_variable = &value;
// No warning
const int *const pointer_variable = &value;
TransformValues(default =true) โ provides fixit-hints for value types that automatically addconstif it's a single declaration.
// 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(default =true) โ provides fixit-hints for reference types that automatically addconstif it's a single declaration.
// 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(default =false) โ bestie, this one hands out fixit-hints for pointers when their pointee is not changed. And big ol' caveat: it does not analyze whether the value-pointed-to is unchanged! ๐
And it only wakes up if you've got WarnPointersAsValues set to true. No cap.
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;
๐ misc-coroutine-hostile-raii ๐
Okay so this check detects when objects of certain hostile RAII types persist across suspension points in a coroutine. And what counts as "hostile"? scoped-lockable types, plus types that live on a configurable denylist. ๐จ
Some objects literally require that they get destroyed on the same thread that created them. Back in the day people phrased this as "must be a local variable," assuming local variables always behave that way. But that assumption is wrong with C++20 coroutines, because an intervening co_await may make the coroutine suspend and then get resumed on a different thread. ๐ญ
So: the lifetime of an object that needs to be destroyed on the same thread must not span a co_await or co_yield point. If you create/destroy an object, you have to do it without letting the coroutine suspend in the meantime. Periodt.
Following types are considered hostile:
- Scoped-lockable types: a scoped-lockable object persisting across a suspension point is a whole problem, because the lock it holds could get unlocked by a different thread. That's undefined behaviour, bestie. 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. Like:my::lockable; a::b;::my::other::lockable;. The default value of this option is"std::lock_guard;std::scoped_lock". ๐AllowedAwaitablesListโ a semicolon-separated list of qualified types of awaitable types which can be safely awaited while you've got hostile RAII objects in scope.
co_await-ing an expression of awaitable type counts as safe if the awaitable type is on this list. RAII objects persisting across such a co_await expression are considered safe and hence are not flagged. ๐ซถ
Example usage:
// 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. The default value of this option is the 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 if those headers get included from multiple translation units. ๐
// 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 ๐ง
This check detects cyclic #include dependencies between user-defined headers. ๐
// 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, since they give you a way to organize declarations and definitions shared across multiple source files. But header files can also create problems when they get entangled in complex dependency cycles. Those cycles can cause issues with compilation times, unnecessary rebuilds, and make it harder to understand the overall structure of the code. ๐ญ
So to address all that, a check got developed to detect cyclic dependencies between header files โ aka "include cycles". An include cycle happens when 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. ๐
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. That specialized analysis helps identify and prevent issues related to header file organization. โจ
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. That can lead to faster compile times, improved code quality, and a more maintainable codebase overall. Plus, by making sure header files are organized in a way that avoids cyclic dependencies, developers can make their code easier to understand and modify over time. ๐
Worth noting: only user-defined headers and their dependencies are analyzed. System includes โ like 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 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. ๐
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.
Options
IgnoredFilesListโ gives you a way to exclude specific files/headers from the warnings raised by a check. You do it 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. ๐ โโ๏ธ
๐ธ misc-include-cleaner ๐ธ
Checks for unused and missing includes. It generates findings only for the main file of a translation unit. Findings correspond to โ https://clangd.llvm.org/design/include-cleaner.
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"
see how bar.h is doing the absolute most and it's correct, while foo.h is just standing there contributing nothing, and baz.h never even got invited but the code is still using it? yeah. that's the whole vibe. ๐
Options ๐
IgnoreHeadersโ a semicolon-separated list of regexes that turns off inserting/removing header files that match the regex as a suffix. like,foo/.*kills insertion/removal for every single header hanging out under the directoryfoo. by default? nothing gets ignored, bestie. no headers ignored, periodt.DeduplicateFindingsโ a boolean that decides whether the check should deduplicate findings for the same symbol. defaults totrue. ๐ซถ
misc-misleading-bidirectional ๐
warns you about unterminated bidirectional unicode sequences, catching a potential attack exactly like the one described in the Trojan Source attack. scary stuff, ngl.
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;
}
misc-misleading-identifier ๐
catches identifiers that have Unicode characters with right-to-left direction in them. why does that matter? bc they can make you misread a whole statement line, fr fr โ same deal as Trojan Source.
here's what that sneaky-looking code looks like:
#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", ื);
}
misc-misplaced-const ๐ญ
this one diagnoses when a const qualifier lands on a typedef/using that points to a pointer type instead of landing on the pointee. why is that a problem? bc it's lowkey misleading โ devs read it and think the const applies to the pointee, but nope, it applies to the pointer. the audacity.
so like, in this code right here, the resulting type is int * const and not 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 stays quiet when the underlying typedef/using type is a pointer to a const type or a function pointer type. why? bc the const is way less likely to be mistaken there โ it would be redundant (or flat-out disallowed) on the underlying pointee type anyway. ๐
misc-new-delete-overloads ๐ฅ
cert-dcl54-cpp redirects here as an alias for this check.
this check flags overloaded operator new() and operator delete() functions that don't have a matching free store function defined in the same scope. example: it'll flag a class implementing a non-placement operator new() when that class also doesn't define a non-placement operator delete(). you gotta do both, bestie, they're a package deal. ๐
things it does not flag:
- implicitly-defined operators
- deleted or private operators
- 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. โ
misc-no-recursion ๐
finds strongly connected functions โ it analyzes the call graph for SCC's (Strongly Connected Components) that are loops โ then diagnoses every function in the cycle, and shows you one example of a possible call graph loop (recursion). icon behavior honestly.
References:
- CERT C++ Coding Standard rule 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..
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.
this check flags dereferences and non-pointer declarations of objects that were never meant to be passed by value โ think C FILE objects, or POSIX pthread_mutex_t objects. do not copy those. seriously.
this check corresponds to CERT C++ Coding Standard rule 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 have non-static data members plus user-declared non-static member functions, and then diagnoses every data member declared with a non-public access specifier. the glow-up here is: make those data members private and access them through member functions instead of just flashing them at derived classes or class consumers. main character energy only. โจ
Options ๐
IgnoreClassesWithAllMemberVariablesBeingPublicโ lets you completely ignore classes when every member variable in that class is declared with apublicaccess specifier. like, fine, whatever, do your thing.IgnorePublicMemberVariablesโ lets you ignore (aka not diagnose) all the member variables declared with apublicaccess specifier.
misc-redundant-expression ๐คก
detects redundant expressions โ the kind that are typically straight-up bugs from copy-paste. we've all been there. ๐ซ
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
Floats get handled too โ except when NaNs are being 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.
swaps assert() out for static_assert() when the condition is evaluable at compile time. and why would you want that? bc the condition of static_assert() is evaluated at compile time โ which is safer and more efficient. slay. ๐
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" โ the one presented for example in "C++ Coding Standards" by H. Sutter and A. Alexandrescu โ plus CERT C++ Coding Standard rule 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 that the user knows that 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 isn't dangerous, but it does tank performance, and we don't do that here. the max 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 user sets this option tostd::numeric_limits<uint64_t>::max()then it reverts back 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, plus definitions with a good return type but wrong return statements. the rules, bestie:
- The return type must be
Class&. - The assignment may be from the class type by value,
constlvalue reference, non-const rvalue 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 โจ
finds unique_ptr::reset(release()) and replaces it with std::move().
Example:
std::unique_ptr<Foo> x, y;
x.reset(y.release()); -> x = std::move(y);
if y is already an rvalue, std::move() doesn't get added. also, x and y can totally be std::unique_ptr<Foo>* too. ๐
Options ๐ฆ
IncludeStyleโ a string specifying which include-style is used,llvmorgoogle. Default isllvm.
misc-unused-alias-decls ๐ป
finds unused namespace alias declarations. like, you gave it a whole new name and then never spoke to it again?? ๐ญ
namespace my_namespace {
class C {};
}
namespace unused_alias = ::my_namespace;
misc-unused-parameters ๐ซถ
finds unused function parameters. and lowkey, unused parameters can signify a bug in the code (like, when a different parameter is used instead, which is giving copy-paste chaos). the suggested fixes either comment the parameter name out, or remove the parameter completely โ the second one only works if all callers of the function are in the same translation unit and can be updated.
this check is similar to the -Wunused-parameter compiler diagnostic and you can use it to prep a codebase for turning that diagnostic on. by default the check is more permissive (see StrictMode). ๐
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โ when it's false (which is the default, bestie), the check just... ignores trivially unused parameters. Meaning: when the function in question has an empty body (and for constructors โ no constructor initializers either). Like, if the body is empty, a human reader is for sure gonna notice an unused param anyway, and there's basically nowhere for a bug to hide. So it doesn't sweat it. ๐IgnoreVirtualโ decides whether virtual method parameters get inspected at all. Set it to true to ignore them. Default is false.
misc-unused-using-decls ๐
Finds unused using declarations. Periodt.
Unused using declarations sitting in header files will not be diagnosed, because those using declarations are part of the header's public API, okay? Leave them alone. โ 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, and I quote, the "superior alternative" according to the C++ Standard. static was proposed for deprecation, but then later un-deprecated to keep C compatibility [1]. Also static is a whole overloaded term โ it means different things in different contexts, so it's giving confusion. ๐
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++. Iconic behaviour tbh.
Examples:
// Bad
static void foo();
static int x;
// Good
namespace {
void foo();
int x;
} // namespace
[1] Undeprecating static
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 you better remove dead code โ and bonus: it gives the compiler more information, which allows for more aggressive optimizations. ๐ฅ
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โ selects what kind of a fix the check should provide. The default isUseStatic.Noneโ don't fix automatically. ๐UseStaticโ addstaticfor internal linkage variable and function.
modernize-avoid-bind ๐ฆ
This check finds uses of std::bind and boost::bind and replaces them with lambdas. Those lambdas will use value-capture, unless reference capture is explicitly requested with std::ref or boost::ref.
It supports arbitrary callables โ including member functions, function objects, and free functions, and all variations thereof. Anything you can pass to the first argument of bind should be diagnosable. Currently the only known case where a fix-it is unsupported is when the same placeholder is specified multiple times in the parameter list. (iykyk ๐ญ)
Given:
int add(int x, int y) { return x + y; }
Then:
void f() {
int x = 2;
auto clj = std::bind(add, x, _1);
}
is replaced by:
void f() {
int x = 2;
auto clj = [=](auto && arg1) { return add(x, arg1); };
}
Real talk: 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. So the lambdas just ate. ๐
Options โญ
PermissiveParameterListโ if the option is set to true, the check will appendauto&&...to the end of every placeholder parameter list. Without this, it's possible for a fix-it to perform an incorrect transformation in the case where the result of thebindis used in the context of a type erased functor such asstd::function, which allows mismatched arguments. 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.
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.
Finds C-style array types and recommends you use std::array<> / std::vector<> instead. All types of C arrays are diagnosed. No survivors. ๐
For parameters of incomplete C-style array type, it would be better to use std::span / gsl::span as the replacement.
However โ fix-its are potentially dangerous in header files and are therefore not emitted right now.
Careful bestie, this one's a known risk area. ๐จ
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, the 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<>. Main character privilege, honestly. ๐
AllowStringArraysโ when set to true (default is false), 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 such as 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
okay so here's the whole situation bestie: clang-tidy literally cannot know whether the header file included by the C++ source file it's currently analyzing is also included by some other C source file. ๐ญ so โ to dodge false-positives and busted fixit-hints โ it just... does not emit reports into header files. lowkey self-care. ๐ซถ
You can set this option to true if you know that the header files in your project are only used by C++ source files. default is false. no cap.
๐ modernize-deprecated-ios-base-aliases
This one sniffs out your usage of the deprecated member types of std::ios_base and swaps in the non-deprecated equivalent wherever one actually exists. โจ it's giving cleanup era.
std::ios_base::io_stateโ replacement:std::ios_base::iostatestd::ios_base::open_modeโ replacement:std::ios_base::openmodestd::ios_base::seek_dirโ replacement:std::ios_base::seekdirstd::ios_base::streamoffโ no replacement ๐std::ios_base::streamposโ no replacement either ๐ญ
๐ฆ modernize-loop-convert
This check converts for(...; ...; ...) loops over to the shiny new range-based loops from C++11. slay. ๐
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
okay so this check has โจ confidence levels โจ and the one you pick decides which transformations actually go through:
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 shows up elsewhere in the loop, we lower our confidence in the transformation โ because there's an increased risk of changing semantics. ๐ฌ transformations for these loops get marked as risky, so they'll only be converted if the minimum required confidence level is set to risky.
int arr[10][20];
int l = 5;
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
See Range-based loops evaluate end() only once for an example of an incorrect transformation when the minimum required confidence level is set to risky. ๐
reasonable (Default)
If a loop calls .end() or .size() after each iteration, that loop's transformation gets marked as reasonable โ so it'll be converted if the required confidence level is set to reasonable (the 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 criteria above to be marked as risky or reasonable get marked safe โ so they'll 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
The converter can also transform iterator loops that use rbegin and rend to loop backwards over a container. ๐ out of the box this just automatically happens in C++20 mode using the ranges library โ but the check can be configured to work without C++20 too, by specifying a function to reverse a range and optionally the header file where that function lives.
UseCxx20ReverseRangesโ when set totrue, convert 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
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.
Comments inside loop headers
Comments inside the original loop header get ignored and deleted when transformed. ๐ they're just gone bestie.
for (int i = 0; i < N; /* This will be deleted */ ++i) { }
Range-based loops evaluate end() only once
The C++11 range-based for loop calls .end() only once, during the initialization of the loop. so if .end() was being called after each iteration in the original loop, the semantics of the transformed loop may differ. ๐ฌ
// 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 inside the body of the loop is considered risky. if the called member function modifies the container, the semantics of the converted loop will differ โ because .end() is only called 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
Same energy here: if operator->() was overloaded to have side effects โ like logging โ the semantics will change. if the iterator's operator->() was used in the original loop, it gets replaced with <container element>.<member> instead, because of the implicit dereference that's part of the range-based for loop. so 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 entirely by accessing and modifying the container through a pointer or reference. ๐ญ the analysis simply can't see it, bestie.
Okay so real talk, bestie: if the container were used directly instead of going through the pointer or the reference, then the transformation below would've only ever been applied at the risky level โ because calling a member function of the container is, yeah, considered risky. And here's the tea: the check cannot identify expressions associated with the container that are different than the one used in the loop header, so the transformation below ends up being performed at the safe level instead. ๐
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
So, range-based for loops are only a thing since OpenMP 5, which means this check should not be used on code that has a compatibility requirement of OpenMP prior to version 5. And ngl, it is intentional that this check does not make any attempts to exclude incorrect diagnostics on OpenMP for loops prior to OpenMP 5. Periodt. โจ
If you want to stop this check from being applied to (and therefore breaking) OpenMP for loops โ while still letting it do its thing on non-OpenMP for loops โ then the recommended move is to use NOLINT (see Suppressing Undesired Diagnostics) on the specific for loops. ๐ซถ
๐ง modernize-macro-to-enum
This one replaces groups of adjacent macros with an unscoped anonymous enum. And the reason that's cute is that using an unscoped anonymous enum guarantees that everywhere the macro token was used before, the enumerator name can be safely used instead. Slay. ๐
You can use this check to enforce the C++ core guideline Enum.1: Prefer enumerations over macros, within the constraints outlined below.
So, which macros are eligible for replacement? They've gotta meet all of these 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. 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 meeting the above constraints is presumed to be a set of values suitable for replacement by an anonymous enum. From there, a developer can give the anonymous enum a name and continue refactoring to a scoped enum if desired. Comments on the same line as a macro definition or between subsequent macro definitions are preserved in the output. No formatting is assumed in the provided replacements, although clang-tidy can optionally format all fixes. โจ
WARNING: Initializing expressions are assumed to be valid initializers for an enum. C requires that enum values fit into anint, but this may not be the case for some accepted constant expressions. For instance1 << 40will not fit into anintwhen the size of anintis 32 bits. ๐
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 spots the creation of std::shared_ptr objects that happens by explicitly calling the constructor plus a new expression, and swaps it out for a call to std::make_shared. Obsessed. ๐
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 replaces those with a call to std::make_shared too:
my_ptr.reset(new MyPair(1, 2));
// becomes
my_ptr = std::make_shared<MyPair>(1, 2);
๐ธ Options
MakeSmartPtrFunctionโ A string specifying the name of make-shared-ptr function. Default isstd::make_shared.MakeSmartPtrFunctionHeaderโ A string specifying the corresponding header of 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 to non-zero, the check does not suggest edits that will transform default initialization into value initialization, as this can cause performance regressions. Default is1.
๐ฆ modernize-make-unique
Same vibe, different smart pointer: this check finds std::unique_ptr objects created by explicitly calling the constructor and a new expression, and replaces it with a call to std::make_unique, which 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);
And it also finds calls to std::unique_ptr::reset() with a new expression, replacing 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 make-unique-ptr function. Default isstd::make_unique.MakeSmartPtrFunctionHeaderโ A string specifying the corresponding header of 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 to non-zero, the check does not suggest edits that will transform default initialization into value initialization, as this can cause performance regressions. Default is1.
โญ modernize-min-max-use-initializer-list
This one replaces nested std::min and std::max calls with an initializer list where applicable. Cute and tidy. ๐
For instance, consider this 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
While this check simplifies the code and makes it more readable, it may cause performance degradation for non-trivial types, because objects need to be copied into the initializer list. ๐
To avoid that, it's 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 here's the lore: with move semantics added to the language and the standard library updated with move constructors added for many types, it's now 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 are 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. Which 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. ๐ญ
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 ๐ฆ
okay so when delayed template parsing is enabled, the constructors that live inside templated contexts โ like templated constructors, constructors in class templates, constructors of inner classes of template classes, etc. โ do NOT get transformed. ๐
Fun fact: delayed template parsing is on by default on Windows as a Microsoft extension โ see the Clang Compiler User's Manual โ Microsoft extensions section, bestie.
You can flip it on with the -fdelayed-template-parsing flag and off with -fno-delayed-template-parsing. ๐
Example, because obviously:
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.
Options โจ
IncludeStyleโ a string saying which include-style you're rocking, eitherllvmorgoogle. Default isllvm. ๐ValuesOnlyโ when it'strue, the check only yells about copied parameters that are already passed by value. Default isfalse. ๐
modernize-raw-string-literal ๐
This check selectively swaps out string literals that have escaped characters in them for raw string literals. Iconic behaviour, honestly.
Example, slay:
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]\))"};
So the way this works: the presence of any of the following escapes can make the string get converted into a raw string literal: \\, \', \", \?, and octal or hexadecimal escapes for printable ASCII characters. ๐
Also bestie โ a string literal that contains only escaped newlines is a super common way of writing lines of text output. If you shoved physical newlines in there with a raw string literal, it would probably tank the readability, so those string literals are left unchanged. ๐ซถ
And an escaped horizontal tab, form feed, or vertical tab prevents the string literal from being converted at all โ because a horizontal tab, form feed or vertical tab sitting in the source code is genuinely not visually obvious. ๐
Options โจ
DelimiterStemโ a custom delimiter you can use 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 whether shorter non-raw string literals get replaced with longer raw string literals. Setting this option totrueturns that replacement on. The default value isfalse(so shorter literals are not replaced). ๐ฆ
modernize-redundant-void-arg ๐ธ
Find and yeet those redundant void argument lists. Bye bestie. ๐
Examples:
- Initial code:
int f(void);โ Code with applied fixes:int f(); - Initial code:
int (*f(void))(void);โ Code with applied fixes:int (*f())(); - Initial code:
typedef int (*f_t(void))(void);โ Code with applied fixes:typedef int (*f_t())(); - Initial code:
void (C::*p)(void);โ Code with applied fixes:void (C::*p)(); - Initial code:
C::C(void) {}โ Code with applied fixes:C::C() {} - Initial code:
C::~C(void) {}โ Code with applied fixes:C::~C() {}
modernize-replace-auto-ptr ๐
This check replaces all the uses of the deprecated class std::auto_ptr with std::unique_ptr (which showed up in C++11, obviously). The transfer of ownership โ the whole thing done by the copy-constructor and the assignment operator โ gets changed to match std::unique_ptr usage by using explicit calls to std::move(). โจ
Migration example:
-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));
}
And because std::move() is a library function declared in <utility>, you might need to add that include. The check will add the include directive when it's necessary. ๐ซถ
Known Limitations ๐ญ
- If headers modification is not activated, or if a header isn't allowed to be changed, this check will produce broken code (a compilation error, bestie ๐) โ where the headers' code stays unchanged while the code using them gets changed.
- Client code that declares a reference to an
std::auto_ptrcoming from code that can't be migrated (like a header from a 3rd party library) will produce a compilation error after migration. Why? Because the type of the reference gets changed tostd::unique_ptrbut the type returned by the library won't change, so you'd be binding a reference tostd::unique_ptrfrom anstd::auto_ptr. This pattern honestly doesn't make much sense, and usuallystd::auto_ptrare stored by value anyway (otherwise, what is even 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. Not even a little bit.
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. Periodt.
So like โ before the delete keyword got introduced in C++11, the common practice was to declare a copy constructor and an assignment operator as private members. That effectively makes them unusable to the public API of a class. Classic era behaviour. ๐
But 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 just delete the methods entirely. Main character move, honestly. ๐
When you run this check on 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. Just a lil heads up bestie. ๐ซถ
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 them with std::shuffle. Because in C++17, std::random_shuffle will no longer be available, so we need to replace it. No cap. ๐ญ
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()()));
And 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. That's just how it is bestie. ๐
okay so real talk bestie ๐ one thing you NEED to know here: std::random_device is expensive to initialize. like, not cheap-expensive, actually-expensive. so if you're dropping this code somewhere performance-critical, you probably wanna initialize it elsewhere. ๐ซถ
and there's a second thing, and it's giving hmm: the seeding quality of the suggested fix is quite poor. like, std::mt19937 has an internal state of 624 32-bit integers, but it's 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 โจ
this one replaces explicit calls to the constructor in a return with a braced initializer list. the way it works is so clean โ the return type is not needlessly duplicated in the function definition and the return statement. iconic behavior fr fr ๐
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 if you're on ancient code, this one's not for you ๐
๐ฆ modernize-type-traits ๐ฆ
converts standard library type traits of the form traits<...>::type and traits<...>::value into traits_t<...> and traits_v<...> respectively. bestie it's literally just glow-up szn for your traits โจ
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โ if true, don't diagnose traits defined in macros. ๐
note: fixes will never be emitted for code inside of macros. that's just how it is, no cap.
#define IS_SIGNED(T) std::is_signed<T>::value
defaults to false.
๐ 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. chef's kiss, honestly ๐
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 ๐
this check is responsible for using the auto type specifier for variable declarations to improve code readability and maintainability. slay ๐
for example:
std::vector<int>::iterator I = my_container.begin();
// transforms to:
auto I = my_container.begin();
the auto type specifier will only be introduced in situations where the variable type matches the type of the initializer expression. in other words auto should deduce the same type that was originally spelled in the source. however, not every situation should be transformed:
int val = 42;
InfoStruct &I = SomeObject.getInfo();
// Should not become:
auto val = 42;
auto &I = SomeObject.getInfo();
in this example using auto for builtins doesn't improve readability. in other situations it makes the code less self-documenting, impairing readability and maintainability. as a result, auto is used only introduced in specific situations described below. iykyk ๐
Iterators
iterator type specifiers tend to be long and used frequently, especially in loop constructs. since the functions generating iterators have a common format, the type specifier can be replaced without obscuring the meaning of code while improving readability and maintainability. it's giving โจglow-upโจ
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
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 likestd::vector<int>::iteratoris itself atypedefwill 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, use of
autowould cause the type of the variable to be deduced asstd::initializer_list. ๐ญ
New expressions
frequently, when a pointer is declared and initialized with new, the pointee type is written twice: in the declaration type and in the new expression. in this case, the declaration type can be replaced with auto, improving readability and maintainability. not me duplicating my own types ๐
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
frequently, when a variable is declared and initialized with a cast, the variable type is written twice: in the declaration type and in the cast expression. in this case, the declaration type can be replaced with auto, improving readability and maintainability. again with the duplication, bestie ๐
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. ๐ฅ
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.
Options
MinTypeNameLengthโ if the option is set to non-zero (default5), 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 theRemoveStarsoption (see below) is set totrue, 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 totrue(default isfalse), the check will remove stars from the non-typedefpointer types when replacing type names withauto. 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 โญ
finds integer literals which are cast to bool. yes, it's that specific, and yes, it's iconic for it ๐
bool p = 1;
bool f = static_cast<bool>(1);
std::ios_base::sync_with_stdio(0);
bool x = p ? 1 : 0;
// transforms to
Options ๐
IgnoreMacrosโ if you set this totrue, the check will not give warnings inside macros. default istrue.
โจ modernize-use-constraints โจ
okay so this one swaps std::enable_if out for C++20 requires clauses. it's giving glow-up. ๐
std::enable_if is a SFINAE mechanism for picking the function or class template you actually want, based on type traits or other requirements. the problem? enable_if changes the meta-arity of the template, and it's got other adverse side effects lurking in your code. C++20 rolls up with concepts and constraints as a way cleaner, language-provided solution for the exact 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, it'll 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 or for function parameters โ are not currently supported by this tool. Other variants such as boost::enable_if are not currently supported by this tool either. ๐
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.
๐ธ modernize-use-default ๐ธ
this check has been renamed to modernize-use-equals-default. that's the whole thing, bestie. nothing else to say. ๐
๐ modernize-use-default-member-init ๐
This check converts constructors' member initializers into the new default member initializers in C++11. Other member initializers that match the default member initializer are removed. This can reduce repeated code or allow use of '= default'. less typing, fewer bugs, we love to see it. ๐ซถ
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: Only converts member initializers for built-in types, enums, and pointers. The readability-redundant-member-init check will remove redundant member initializers for classes.
Options โจ
UseAssignmentโ if this option is set totrue(default isfalse), the check will initialize members with an assignment. For example:
struct A {
A() {}
A(int i) : i(i) {}
int i = 5;
double j = 10.0;
};
IgnoreMacrosโ if this option is set totrue(default istrue), the check will not warn about members declared inside macros.
๐ฆ modernize-use-designated-initializers ๐ฆ
Finds initializer lists for aggregate types which could be written as designated initializers instead.
ngl, with plain initializer lists it is so easy to introduce bugs when you add new fields in the middle of a struct or class type. Same confusion energy when you change the order of fields. ๐ญ
C++20 supports the designated initializer syntax for aggregate types. By applying it, we can always be sure that aggregates are constructed correctly, because every variable being initialized is referenced by its name. main character energy for real.
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 an issue when i and j are swapped in the declaration of S. ๐
Even when compiling in a language version older than C++20, depending on your compiler, designated initializers are potentially supported. Therefore, the check is by default restricted to C99/C++20 and above. Check out the options -Wc99-designator to get support for mixed designators in initializer list in C and -Wc++20-designator for support of designated initializers in older C++ language modes.
Options ๐
IgnoreMacrosโ The valuefalsespecifies that components of initializer lists expanded from macros are not checked. The default value istrue.IgnoreSingleElementAggregatesโ The valuefalsespecifies that even initializers for aggregate types with only a single element should be checked. The default value istrue.RestrictToPODTypesโ The valuetruespecifies that only Plain Old Data (POD) types shall be checked. This makes the check applicable to even older C++ standards. The default value isfalse.StrictCStandardComplianceโ When set tofalse, the check will not restrict itself to C99 and above. The default value istrue.StrictCppStandardComplianceโ When set tofalse, the check will not restrict itself to C++20 and above. The default value istrue.
๐ฅ modernize-use-emplace ๐ฅ
The check flags insertions to an STL-style container done by calling the push_back, push, or push_front methods with an explicitly-constructed temporary of the container element type. In this case, the corresponding emplace equivalent methods result in less verbose and potentially more efficient code. Right now the check doesn't support insert. It also doesn't support insert functions for associative containers because replacing insert with emplace may result in speed regression โ but it might get support with some addition flag in the future. ๐
The ContainersWithPushBack, ContainersWithPush, and ContainersWithPushFront options are used to specify the container types that support the push_back, push, and push_front operations respectively. The default values for these options are as follows:
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. This 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, as 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 is able to 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 modified by the TupleTypes option; custom make functions can be modified by the TupleMakeFunctions option.
The other situation is 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 this case the calls of push_back 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));
This is because replacing it with emplace_back could cause a leak of this pointer if emplace_back would throw exception before emplacement (e.g. not enough memory to add a new element).
For more info read item 42 - "Consider emplacement instead of insertion." of Scott Meyers "Effective Modern C++".
The default smart pointers that are considered are std::unique_ptr, std::shared_ptr, std::auto_ptr. To specify other smart pointers or other classes use the SmartPointers option.
Check also 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) - if the argument 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 will ignore implicitly constructed arguments ofpush_back, e.g.
std::vector<std::string> v;
v.push_back("a"); // Ignored when IgnoreImplicitConstructors is `true`.
SmartPointersโ a semicolon-separated list of class names for your โจ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 yeeted out ofpush_backcalls and turned intoemplace_backinstead ๐EmplacyFunctionsโ semicolon-separated list of containers without their template parameters, plus some emplace-like method of that container. Example:vector::emplace_back. Those methods get vibe-checked for improper use, and the check will report when a temporary is being created for absolutely no reason ๐ญ
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. like. look at that glow up ๐ฆ
modernize-use-equals-default
This check swaps out the default bodies of special member functions for = default;. The way that explicitly defaulted function declarations open up more optimization opportunities, because the compiler might treat explicitly defaulted functions as trivial ๐
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. bestie we're working on it ๐
Options
IgnoreMacrosโ if set totrue, the check will not give warnings inside macros and will ignore special members whose bodies contain macros or preprocessor directives. Default istrue.
modernize-use-equals-delete
Identifies unimplemented private special member functions and recommends using = delete for them. On top of that, it recommends relocating any deleted member function from the private section to the public section. it's giving glow-up, it's giving relocation ๐
Before C++11 showed up, the primary method to effectively "erase" a particular function was to declare it as 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). classic chaotic era.
However, subsequent to the advent of C++11, a more conventional approach emerged for achieving this purpose. It involves flagging functions as = delete and keeping them in the public section of the class. slay.
To prevent false positives, this check is only active within a translation unit where all other member functions have been implemented. The check will generate partial fixes by introducing = delete, but you, the user, are responsible for manually relocating functions to the public section. no cap, that part is 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;
};
Options
IgnoreMacrosโ if this option is set totrue(default istrue), the check will not warn about functions declared inside macros.
modernize-use-integer-sign-comparison
Replace comparisons between signed and unsigned integers with their safe C++20 std::cmp_* alternative, if available. ๐ง
The check provides a replacement only for C++20 or later โ otherwise it highlights the problem and expects the user to fix it manually. sorry bestie, you're on your own for that one ๐ญ
Examples of fixes created by the check:
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 specifying which include-style is used,llvmorgoogle. Default isllvm.EnableQtSupportโ makes the C++17q20::cmp_*alternative available for Qt-based applications. Default isfalse.
modernize-use-nodiscard
Adds [[nodiscard]] attributes (introduced in C++17) to member functions in order to highlight at compile time which return values should not be ignored. main character energy for your return values honestly โจ
Member functions need to satisfy the following conditions to be considered by this check:
- no
[[nodiscard]],[[noreturn]],__attribute__((warn_unused_result)),[[clang::warn_unused_result]]nor[[gcc::warn_unused_result]]attribute, - non-
voidreturn type, - non-template return types,
constmember function,- non-variadic functions,
- no non-
constreference 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.
Such functions have no means of altering any state or passing values other than via the return type. Unless the member functions are altering state via some external call (e.g. I/O). yeah. exceptions exist, we know ๐
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 useful when maintaining source code that needs to compile with a pre-C++17 compiler. throwback era, respect ๐ซก
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. define your macro bestie, or nothing happens ๐
NOTE: For alternative__attribute__syntax options to mark functions as[[nodiscard]]in non-c++17 source code. See โ https://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 (introduced in C++11). By default this check will replace throw() with noexcept, and throw(<exception>[,...]) or throw(...) with noexcept(false). out with the old, in with the noexcept ๐ฅ
Example
void foo() throw();
void bar() throw(int) {}
transforms to:
void foo() noexcept;
void bar() noexcept(false) {}
Options
ReplacementStringโ users can useReplacementStringto specify a macro to use instead ofnoexcept. This is useful when maintaining source code that uses custom exception specification marking other thannoexcept. Fix-it hints will only be generated for non-throwing specifications. iykyk.
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
The check converts the usage of null pointer constants (e.g. NULL, 0) to use the new C++11 and C23 nullptr keyword. the upgrade your pointers deserved ๐
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 to match pointer types for which implicit casts will be ignored. Default value:std::_CmpUnspecifiedParam::;^std::__cmp_cat::__unspec.NullMacrosโ comma-separated list of macro names that will be transformed along withNULL. By default this check will only replace theNULLmacro and will skip any similar user-defined macros. don't touch my customs, that's the rule โ
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 (introduced in C++11) to overridden virtual functions and removes virtual from those functions as it is not required. decluttering era ๐
virtual on non base class implementations was used to help indicate to the user that a function was virtual. C++ compilers did not use the presence of this to signify an overridden function. so it was basically decoration. cute but extra.
In C++11 override and final keywords were introduced to allow overridden functions to be marked appropriately. Their presence allows compilers to verify that an overridden function correctly overrides a base class implementation. the compiler is checking your receipts now ๐งพ
This can be useful as 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, instructs this check to ignore virtual function overrides that are part of template instantiations. Default isfalse.AllowOverrideAndFinalโ if set totrue, this check will not diagnoseoverrideas redundant withfinal. This is useful when code will be compiled by a compiler with warning/error checking flags requiringoverrideexplicitly on overridden members, such asgcc -Wsuggest-override/gcc -Werror=suggest-override. Default isfalse.OverrideSpellingโ tells the check which macro to use instead of plainoverride. So iconic when you're babysitting source code that also has to compile with a pre-C++11 compiler and can't just raw-dog the keyword. ๐FinalSpellingโ same deal but forfinal: the macro that stands in for it, again for the pre-C++11-compatible codebase girlies. โจ
๐ก NOTE: for more info on how override actually works, go look at โ https://en.cppreference.com/w/cpp/language/override โ it's giving documentation, iykyk.
modernize-use-ranges
Spots calls to the standard library's old-school iterator algorithms that could be living their best life as a ranges version instead. glow up fr fr ๐ฅ
โจ 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, no cap:
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: some range algorithms for vector<bool> need C++23 because it uses proxy iterators. the way that proxy iterator requirement sneaks up on you ๐ญ
๐ซ Reverse Iteration ๐ซ
If the calls are made with reverse iterators on containers, the code gets fixed using the std::views::reverse adaptor. ๐ฆ
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. pick your fighter ๐UseReversePipeโ whentrue(defaultfalse), fixes that involve reverse ranges will use the pipe adaptor syntax instead of the function syntax. main character syntax choice
std::find(Items.rbegin(), Items.rend(), 0);
Transforms to:
std::ranges::find(Items | std::views::reverse, 0);
modernize-use-starts-ends-with
Checks for all the roundabout ways people express starts_with and ends_with and suggests the simpler method when it's available. the way that this just works โจ Notably, it works with std::string and std::string_view. ๐
๐ Covered scenarios ๐
u.find(v) == 0โu.starts_with(v)u.rfind(v, 0) != 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, if you configure them โ into C++20's std::format, or into another function via a configuration option, modifying the format string appropriately and yeeting the now-unnecessary calls to std::string::c_str() and std::string::data(). ๐
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 exact same format-string-conversion algorithm as modernize-use-std-print, and its shortcomings and its behaviour in combination with macros are described in the documentation for that check. ๐
๐ 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 make sure the output matches what a simple wrapper forstd::sprintfthat accepted a C-style variable argument list would have produced. For example, withStrictModeenabled,
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 make sure the output stays the unsigned representation of -42 and the signed representation of 0xffffffff (often 4294967254 and -1 respectively). When false โ which is the default โ those casts won't be added, which may cause a change in the output. bestie, that's a behaviour change, be aware ๐ญ 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 used to replace the function set by theStrFormatLikeFunctionsoption instead of the defaultstd::format. It's expected that the function provides an interface compatible withstd::format. A suitable candidate would befmt::format. fmt girlies rise ๐ซถFormatHeaderโ the header that must be included for the declaration ofReplacementFormatFunction, so a#includedirective can be added if it's required. IfReplacementFormatFunctionisstd::formatthen this option defaults to<format>, otherwise this option defaults to nothing and no#includedirective gets added. ๐
modernize-use-std-numbers
Finds constants and function calls to math functions that could be replaced with C++20's mathematical constants from the numbers header, and offers fix-it hints. โจ It does not match the use of variables holding 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 get matched and replaced with the std::numbers constant. And the use of macros gets replaced with the corresponding std::numbers constant, rather than changing the macro definition. so thorough, we love to see it ๐ฅ
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, bestie, and it's literally the detection threshold for when a literal counts as matching a constant. A literal matches a constant whenabs(literal - constant) < DiffThresholdcomes out true. Default is0.001. ๐IncludeStyleโ a string that says which include-style we're doing, eitherllvmorgoogle. Default isllvm. ๐
๐ modernize-use-std-print ๐
okay so this one is iconic: it takes calls to printf, fprintf, absl::PrintF and absl::FPrintf and turns them into the equivalent calls to C++23's std::print or std::println, whichever fits, and it rewrites the format string to match. ๐ฆ Both the replaced and the replacement functions are customisable through config options, no cap. And listen โ every argument that's the result of a call to std::string::c_str() or std::string::data() gets that now-unnecessary call stripped out, same energy as the readability-redundant-string-cstr check.
In other words, it takes lines like:
fprintf(stderr, "The %s is %3d\n", description.c_str(), value);
and turns them into:
std::println(stderr, "The {} is {:3}", description, value);
Heads up though: if the ReplacementPrintFunction or ReplacementPrintlnFunction options are left at their defaults (or explicitly set to them), then this check is only enabled when you're on -std=c++23 or later. ๐
Macros starting with PRI and __PRI from <inttypes.h> get expanded, escaping is handled, and adjacent strings get concatenated into a single StringLiteral before the format string is converted. Any other macro in the format string will make a warning message get emitted and no conversion happens at all. The converted format string is always a single string literal. ๐
It doesn't do a bad job, but it's not perfect. Specifically:
- It assumes the format string is correct for the arguments. If you get any warnings when compiling with
-Wformatthen misbehaviour is possible. ๐ญ - By the time the check runs, the AST has a single
StringLiteralfor the format string with escapes already expanded. The check tries to reconstruct escape sequences, but they may not be the same as what you wrote (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. The 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. Which means any invocation that uses the return value will not be converted. Unfortunately this currently includes explicitly-casting tovoid. Deficiencies in this check also mean any invocations inside GCC compound statements cannot be converted even if the resulting value isn't used. ๐
If a conversion would be incomplete or unsafe then the entire invocation is left unchanged, periodt.
If the call is deemed suitable for conversion then:
printf,fprintf,absl::PrintF,absl::FPrintFand any functions specified by thePrintfLikeFunctionsoption orFprintfLikeFunctionsget replaced with the function named by theReplacementPrintlnFunctionoption if the format string ends with\n, orReplacementPrintFunctionotherwise.- the format string gets rewritten into the
std::formatterlanguage. If a\nis found at the end of the format string and it's not preceded byrthen it gets removed andReplacementPrintlnFunctionis used instead ofReplacementPrintFunction. - any arguments that corresponded to
%pspecifiers thatstd::formatterwouldn't accept get wrapped in astatic_casttoconst void *. - any arguments that corresponded to
%sspecifiers where the argument is ofsigned charorunsigned chartype get wrapped in areinterpret_cast<const char *>. - any arguments where the format string and the parameter differ in signedness get wrapped in an appropriate
static_castifStrictModeis enabled. - any arguments that end in a call to
std::string::c_str()orstd::string::data()get that call removed. ๐ซถ
โจ Options โจ
StrictModeโ whentrue, 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, so that the output matches whatprintfdid. 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));
so the output keeps being the unsigned representation of -42 and the signed representation of 0xffffffff (often 4294967254 and -1 respectively, iykyk). When false โ which is the default โ those casts are not 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's 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's empty. ๐ธReplacementPrintFunctionโ the function that will be used to replaceprintf,fprintfetc. during conversion instead of the defaultstd::print, when the original format string does not end with\n. It's expected that the function provides an interface compatible withstd::print. A suitable candidate would befmt::print. โญReplacementPrintlnFunctionโ the function that will be used to replaceprintf,fprintfetc. during conversion instead of the defaultstd::println, when the original format string does end with\n. It's expected that the function provides an interface compatible withstd::println. A suitable candidate would befmt::println. ๐PrintHeaderโ the header that must be included for the declaration ofReplacementPrintFunction, so that a#includedirective can be added if required. IfReplacementPrintFunctionisstd::printthen this option defaults to<print>, otherwise this option defaults to nothing and no#includedirective gets added. ๐
๐ modernize-use-trailing-return-type ๐
This one rewrites function signatures to use a trailing return type (which, bestie, was introduced in C++11). The transformation is purely stylistic โ the return type that was sitting before the function name gets 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;
transforms to:
auto f1() -> int;
inline auto f2(int arg) -> int noexcept;
virtual auto f3() const && -> float = delete;
๐ญ Known Limitations ๐ญ
The following categories of return types can't 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. That location includes, like, 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. ๐
๐ modernize-use-transparent-functors ๐
Prefer transparent functors over non-transparent ones, bestie. When you use transparent functors, the type doesn't need to be repeated. The code is easier to read, easier to maintain, and less prone to errors. It's also not possible to introduce unwanted conversions. ๐ฅ
// 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. The following case will be left untouched to preserve the semantics:
// Non-transparent functor
std::map<const char *, std::string, std::greater<std::string>> s;
โจ Options โจ
SafeModeโ bestie, if you flip this totrue, the check will not call out cases where using a transparent functor can't be guaranteed to produce identical results as the original code. Default value? It'sfalse. ๐
This check needs C++14 or higher to even run, no cap.
๐ธ modernize-use-uncaught-exceptions ๐ธ
This one's a lil snitch, but in a helpful way: it warns on any calls to std::uncaught_exception and swaps them for std::uncaught_exceptions, because std::uncaught_exception got deprecated in C++17. It's giving glow-up energy. โจ
Here's what kind of stuff it catches, and what it turns them into:
#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
}
And after the fixes get applied, it's looking like this:
#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 ๐ฆ
This check converts the usage of typedef with the using keyword. Glow up, 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 inside extern "C" { ... } blocks. Respecting the boundary, iconic. ๐
extern "C" {
typedef int InExternC; // Left intact.
}
This check requires C++11 or higher to run.
โจ Options โจ
IgnoreMacrosโ set it totrueand the check will not give warnings inside macros. Default istrue.IgnoreExternCโ set it totrueand the check will not give a 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. The vibe check here: buffers should be passed as a single pointer or array. But because MPI function signatures specify void * for their buffer types, insufficiently dereferenced buffers can sneak right on through โ like double pointers or multidimensional arrays โ and no compiler warning gets emitted. Rude, honestly. ๐
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 actually match for the MPI functions you used. Every MPI datatype defined by the MPI standard (3.1) gets verified by this check. User defined typedefs, custom MPI datatypes and null pointer constants are skipped in the course of verification โ they get a pass, no questions asked.
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.
Why do we care? Because it makes tests less fragile โ so many of them improperly rely on pointer equality for strings that have equal values, and that assumption is not guaranteed by the language. Not today, satan. ๐ซถ
๐ objc-avoid-nserror-init ๐
Finds improper initialization of NSError objects.
Per Apple developer documentation, we should always use the factory method errorWithDomain:code:userInfo: to create new NSError objects instead of [NSError alloc] init]. Otherwise it'll lead to a warning message during runtime. We do not want the runtime warning, bestie. ๐ญ
The corresponding information about NSError creation lives here: 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. Heads up: the category implementation will override any -dealloc in the class implementation, potentially causing issues. ๐จ
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. Chaos behavior, literally.
๐ objc-forbidden-subclassing ๐
Finds Objective-C classes which are subclasses of classes which are not designed to be subclassed.
By default, it 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. Verifying the actual implementation of -hash is the developer's responsibility, e.g., through the addition of unit tests to verify the implementation. That part's on you, bestie. ๐
๐ง 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 2: Input Date: 29 December 2014 ; Format String:
ww-YYYY (DD-yyyy)Output string:01-2015 (363-2014)(This is correct)
- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY+WW: Week year with week of a calendar year (unlessyyyyis also specified).- Example 1: Input Date: 29 December 2014 ; Format String:
YYYY-WWOutput string:2015-05(Wrong because it's 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)(This is correct)
- Example 1: Input Date: 29 December 2014 ; Format String:
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 because it's not the 5th Monday of January in 2015) - Example 2: Input Date: 29 December 2014 ; Format String:
ww-YYYY (F-EE-MM-yyyy)Output string:01-2015 (5-Mon-12-2014)(This is correct)
- Example 1: Input Date: 29 December 2014 ; Format String:
objc-nsinvocation-argument-lifetime</p> ๐ธ
Bestie, this one is out here catching calls to NSInvocation methods under ARC that just... don't have proper argument object lifetimes. Rude of them. When you pass Objective-C objects as parameters to the NSInvocation methods getArgument:atIndex: and getReturnValue:, those values get copied by value into the argument pointer, and that leads to incorrect releasing behavior if the object pointers aren't declared __unsafe_unretained. No cap.
So for code like this:
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:
// "id _returnValue" is declaration of instance variable of class.
[invocation getReturnValue:&self->_returnValue];
objc-property-declaration ๐
This check finds property declarations in Objective-C files that are not following the pattern of property names in Apple's programming guide. The property name should be in the format of Lower Camel Case, periodt.
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: โ
For a full list of well-known acronyms: โ
The corresponding style rule: โ
The check will also accept property declared in 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 ๐ฆ
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. ๐ So like, don't do that.
openmp-exception-escape ๐ฅ
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. ๐ญ
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 ๐
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 to use the default(none) clause.
Using 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 clarity. ๐
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 โก
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.
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. Slay. ๐
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 ๐ง
Recommends the smallest possible underlying type for an enum or enum class based on the range of its enumerators. 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.
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.
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โ Option is used to ignore 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. fr fr.
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 only ever fires on loop variables whose type is expensive to copy โ like, not trivially copyable, or it's got a non-trivial copy constructor or destructor. bestie we are NOT deep-copying your entire container every single iteration ๐
And to make sure it's actually safe to swap that copy for a const reference, it runs this little heuristic:
- The loop variable is
constqualified. โจ - The loop variable isn't
const, but onlyconstmethods or operators get invoked on it โ or it's used as aconstreference or value argument in constructors or function calls.
Options ๐
WarnOnAllAutoCopiesโ whentrue, it will warn on any use ofautoas the type of the range-based for loop variable. Default isfalse.AllowedTypesโ a semicolon-separated list of names of types that are allowed to be copied on 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 typename (i.e.namespace::Type), otherwise it is matched against only the type name (i.e.Type).
performance-implicit-cast-in-loop
Okay so this check got a rebrand, and honestly slay: it has been renamed to performance-implicit-conversion-in-loop. the old name is retired, do not perceive it ๐
performance-implicit-conversion-in-loop
This warning shows up in a range-based loop where the loop variable is a const ref type and the type of that variable doesn't match the one the iterator actually returns. Which means an implicit conversion is happening, and that can, for example, result in expensive deep copies. ngl that's giving wasteful ๐ญ
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 just use const auto& instead of writing the type out by hand. let the compiler cook ๐ซถ
performance-inefficient-algorithm
Warns on inefficient use of STL algorithms on associative containers. ๐
Here's the tea: associative containers implement some of these algorithms as methods, and those methods should be preferred over the ones in the algorithm header. Why? Because the methods can take advantage of the order of the elements. the container literally knows where everything is, bestie โจ
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 that comes from concatenating strings with 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. reallocation jumpscare ๐
It can also find calls that add an element to a protobuf repeated field in a loop without calling Reserve() before the loop. Calling Reserve() first can avoid unnecessary memory reallocations. manifesting one single allocation for you ๐ง
Currently, the check only detects the 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โ whentrue, 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
This 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 aconstreference argument.
In all three cases, the check will suggest a fix that removes the std::move(). the move was never moving, bestie, it was just vibes ๐ญ
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โ iftrue, enables detection of trivially copyable types that do not have a move constructor. Default istrue.CheckMoveToConstRefโ iftrue, enables detection ofstd::move()passed as aconstreference argument. Default istrue.
performance-move-constructor-init
Heads up: "cert-oop11-cpp" redirects here as an alias for this check. same check, different name, it contains multitudes ๐
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. you said move and then you copied?? be so serious ๐
performance-no-automatic-move
Finds local variables that cannot be automatically moved due to constness. ๐ซ
Under certain conditions, 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. the const was the villain all along ๐ญ
Example [1]:
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. Cool is the main character, NotCool is the flop ๐
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. no auto-fix here, you have to use your brain bestie ๐ง
-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. ๐ฅ
Okay so here's the lore. 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. provenance loss is giving identity theft ๐
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 the 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]
}
The check will rightfully diagnose that cast. caught in 4k ๐ธ
But when provenance concealment is not the goal of the code, but an accident, this example can be rewritten as follows, without using an integer to pointer cast:
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
The check flags user-defined destructors marked with noexcept(expr) where expr evaluates to false (but is not a false literal itself). ๐
When a destructor is marked as noexcept, it assures the compiler that no exceptions will be thrown during the destruction of an object, which allows the compiler to perform certain optimizations such as omitting exception handling code. so if you lied about it, that's a whole problem, bestie ๐
๐ธ performance-noexcept-move-constructor
Okay so bestie, this check is out here clocking your user-defined move constructors and assignment operators whenever they are not marked noexcept, or whenever they're marked noexcept(expr) and that expr evaluates to false (but ngl it does not flag an actual false literal sitting right there, just so we're clear ๐
).
Why does this even matter tho? Because move constructors of all the types used with STL containers, for example, need to be declared noexcept. Otherwise STL will choose copy constructors instead โ the audacity ๐ญ. And the same is valid for move assignment operations. Periodt.
โจ performance-noexcept-swap
This one flags your user-defined swap and iter_swap functions when they're not marked noexcept, or when they're marked noexcept(expr) where expr evaluates to false (again โ not a false literal itself, okay? ๐).
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 allows the compiler to perform certain optimizations such as omitting exception handling code. Iconic behavior honestly ๐
๐ performance-trivially-destructible
Finds types that could be made trivially-destructible by removing out-of-line defaulted destructor declarations.
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) 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 have sufficed to obtain a const reference. Like... why are we copying, bestie ๐
The check is only applied if it is safe to replace the copy by a const reference. This is the case when the variable is const qualified or when it is only used as a const, i.e. only const methods or operators are invoked on it, or it is used as 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 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 is matched against the qualified typename (i.e.namespace::Type), otherwise it is 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 is not tied to mutations of the container. An example are 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 are copied for each invocation, when bestie it would suffice to pass them by const reference. ๐
The check is only applied to parameters 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 value parameter with a const reference the following heuristic is employed:
- the parameter is
constqualified; - the parameter is not
const, but onlyconstmethods or operators are invoked on it, or it is used asconstreference 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, 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);
}
๐ 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 is matched against the qualified typename (i.e.namespace::Type), otherwise it is matched against only the type name (i.e.Type).
๐ธ portability-restrict-system-includes
Checks to selectively allow or disallow a configurable list of system headers.
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) alternatives.
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 there are P0214 alternatives).
Many architectures provide SIMD operations (e.g. x86 SSE/AVX, Power AltiVec/VSX, ARM NEON). It is common that SIMD code implementing the same algorithm, is written in multiple target-dispatching pieces to optimize for different architectures or micro-architectures.
The C++ standard proposal P0214 and its extensions cover many common SIMD operations. By migrating from target-dependent intrinsics to P0214 operations, the SIMD code can be simplified and pieces for different targets can be unified.
Refer to P0214 for introduction and motivation for the data-parallel standard library.
๐ Options
Suggestโ If this option is set totrue(default isfalse), the check will suggest P0214 alternatives, otherwise it only points out the intrinsic function is non-portable.Stdโ The namespace used to suggest P0214 alternatives. If not specified,std::for-std=c++20andstd::experimental::for-std=c++11.
๐ portability-std-allocator-const
Report 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." ๐ (yes bestie, that stray quote is literally in there, we don't ask questions)
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>:
// 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 only compiled with libc++ may accrue such undefined usage. This check finds such code and prevents backsliding while clean-up is ongoing. ๐ซถ
๐ซ 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 yeah โ different compilers, different vibes, and not in a cute way ๐ญ
okay so basically, in these little snippets, the virtual member function is not instantiated by GCC and Clang, but it totally is instantiated by MSVC โ so the snippet sails through on the former compilers and gets flat-out 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;
}
so bestie, if you're on a cross-platform project that has to support MSVC on Windows, you might get hit with compiler errors because certain virtual member functions get instantiated โ ones that other compilers on other platforms just... don't instantiate. ๐ This check highlights those exact virtual member functions for you. very much the friend who points out the thing in your teeth before the photo, except it's a compiler error. ๐
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. no cap, they're just vibes with no consequences.
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 โ aka ternary operators, you know the ones โ can contribute to reduced code readability and comprehension. So they should be split as several statements and stored the intermediate results in temporary variable. fr fr, your future self reading this at 2am deserves better.
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. it's giving "wait, does this return something??" energy. ๐ญ
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 that return statements expanded from macros are not checked. The default value istrue.StrictModeโ The valuefalsespecifies that a direct return statement 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. This can cause maintenance issues, confusion for future developers, and potential compilation problems. it's a whole mess honestly. ๐
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. see? there's always a cuter way. โจ
readability-braces-around-statements
google-readability-braces-around-statements redirects here as an alias for this check. iykyk ๐ฆ
Checks that bodies of if statements and loops (for, do while, and while) are inside braces. put the braces ON, bestie. ๐
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 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. ๐
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
Finds usages of container.count() and container.find() == container.end() which should be replaced by a call to the container.contains() method. ๐ซถ
Whether an element is contained inside a container should be checked with contains instead of count/find because contains conveys the intent more clearly. Furthermore, for containers which permit multiple entries per key (multimap, multiset, ...), contains is more efficient than count because count has to do unnecessary additional work. iconic efficiency moment.
Examples:
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Initial expression โ Result โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ myMap.find(x) == โ !myMap.contains(x) โ
โ myMap.end() โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ myMap.find(x) != โ myMap.contains(x) โ
โ myMap.end() โ โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ if (myMap.count(x)) โ if (myMap.contains(x)) โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ bool exists = โ bool exists = โ
โ myMap.count(x) โ myMap.contains(x) โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ bool exists = โ bool exists = โ
โ myMap.count(x) > 0 โ myMap.contains(x) โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ bool exists = โ bool exists = โ
โ myMap.count(x) >= 1 โ myMap.contains(x) โ
โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ bool missing = โ bool missing = โ
โ myMap.count(x) == 0 โ !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 which should be preferred. โญ
This also ensures that in the case that the container is empty, the data pointer access does not perform an errant memory access. no crashing on empty containers, we don't do that here. ๐ซ
Options
IgnoredContainersโ Semicolon-separated list of containers regexp for which this check won't be enforced. Default is empty.
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-proofing queen behavior. ๐
The check issues 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.
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. ๐
After applying modifications as suggested by the check, running the check again might find more opportunities to mark member functions static. yes bestie, run it again, there's more. ๐
Okay so once you've made a member function static, bestie, you'll probs wanna run readability-static-accessed-through-instance to swap out calls like Instance.method() for Class::method(). ๐
๐๏ธ readability-delete-null-pointer
This one's looking at all your if statements where you check whether a pointer exists and then delete it. And likeโฆ that check is doing nothing. Deleting a null pointer has no effect anyway, so the whole if is unnecessary. Not me catching you doing extra work for no reason ๐ญ
int *p;
if (p)
delete p;
๐ธ readability-duplicate-include
Sniffs out duplicate #includes and yeets them. โจ The check keeps a lil list of every file that's been included and watches for repeats. Heads up though: if a macro gets defined or undefined, the included-files list gets cleared.
Examples, because we love a visual:
#include <memory>
#include <vector>
#include <memory>
becomes
#include <memory>
#include <vector>
But because of the intervening macro definitions, this code stays exactly as it is, no touchies:
#undef NDEBUG
#include "assertion.h"
// ...code with assertions enabled
#define NDEBUG
#include "assertion.h"
// ...code with assertions disabled
๐ readability-else-after-return
So the LLVM Coding Standards say to reduce indentation wherever possible and wherever it makes the code easier to read. Early exit is one of the suggested ways to do that. Please, please, do not use else or else if after something that interrupts control flow โ like return, break, continue, throw. Periodt. ๐
Here's 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, it emits a warning for the cases where the check can't output a Fix-It. Those pop up with declarations inside theelsebranch that would end up with an extended lifetime if theelsebranch got removed. Default value istrue.WarnOnConditionVariablesโ whentrue, the check tries to refactor a variable defined inside the condition of theifstatement that's used in theelsebranch defining them, just before theifstatement. This can only happen if theifstatement is the last statement in its parent's scope. Default value istrue.
๐ LLVM alias
There's 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. Sneaky!
This check helps to enforce this LLVM Coding Standards recommendation. ๐ซถ
๐ข readability-enum-initial-value
Enforces a consistent style for how your enumerators get initialized. Three styles, pick your fighter: none, first only, or all initialized explicitly.
An inconsistent style and strictness around defining the initializing value of enumerators can cause issues if the enumeration gets extended with new enumerators that grab their integer representation implicitly. Danger zone.
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 is not allowed. Default istrue.
enum G {
g0 = 1, // Not allowed if AllowExplicitSequentialInitialValues is false.
g1 = 2,
g2 = 3,
๐ง readability-function-cognitive-complexity
Checks the function Cognitive Complexity metric. It's giving brainpower.
The metric is implemented as per the COGNITIVE COMPLEXITY by SonarSource specification version 1.2 (19 April 2017).
โจ Options โจ
Thresholdโ flags functions with Cognitive Complexity exceeding this number. The default is25.DescribeBasicIncrementsโ if set totrue, then for each function exceeding the complexity threshold the check issues additional diagnostics on every piece of code (loop,ifstatement, etc.) that contributes to that complexity. See also the examples below. Default istrue.IgnoreMacrosโ if set totrue, the check ignores code inside macros. Note: also any macro arguments are ignored, even if they should count toward the complexity. Since this might change in the future, this option isn't guaranteed to be forward-compatible. Default isfalse.
๐งฑ Building blocks
There are three basic building blocks of a Cognitive Complexity metric:
Increment
The following structures increase 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
While by itself the nesting level does not change the function's Cognitive Complexity metric, it is tracked, and it's used by the next, third building block. The following 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 that previous basic building block, Nesting level, starts to matter. The following 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. This function has Cognitive Complexity of 0. Iconic behaviour, honestly.
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 the option DescribeBasicIncrements is set to true, it additionally flags the two if statements with the amounts by which they increase to the complexity of the function and the current nesting level. ๐ฅ
๐ง 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.
Checks for large functions based on various metrics. Big function = red flag, bestie.
โจ Options โจ
LineThresholdโ flags functions exceeding this number of lines. The default isnone(ignore the number of lines).StatementThresholdโ flags functions exceeding this number of statements. This may differ significantly from the number of lines for macro-heavy code. The default is800.BranchThresholdโ flags functions exceeding this number of control statements. The default isnone(ignore the number of branches).ParameterThresholdโ flags functions that exceed a specified number of parameters. The default isnone(ignore the number of parameters).NestingThresholdโ flags compound statements which create next nesting level afterNestingThreshold. This may differ significantly from the expected value for macro-heavy code. The default isnone(ignore the nesting level).VariableThresholdโ flags any function whose body declares more variables than this number. The default is none, which means ignore the variable count entirely โ live your truth, bestie. ๐ Heads up tho: function parameters do not count, and neither do variables declared inside lambdas, GNU Statement Expressions, or nested class inline functions. So it's literally just the variables chillin' directly in the body.
๐ธ readability-identifier-length ๐ธ
This check is the โจname policeโจ โ it finds variables and function parameters whose length is too short. The desired name length is configurable, so you get to decide what "too short" means. ๐
Special cases are supported for loop counters and for exception variable names. ๐ฆ
โจ Options โจ
The following options are described below:
MinimumVariableNameLength,IgnoredVariableNamesMinimumParameterNameLength,IgnoredParameterNamesMinimumLoopCounterNameLength,IgnoredLoopCounterNamesMinimumExceptionNameLength,IgnoredExceptionVariableNames
MinimumVariableNameLength
All variables โ other than loop counters, exception names, and function parameters โ are expected to have at least a length of MinimumVariableNameLength (default is 3). Setting it to 0 or 1 disables the check entirely, no cap. ๐ซถ
int i = 42; // warns that 'i' is too short
This check does not have any fix suggestions in the general case, because variable names carry semantic value โ a computer can't just rename your vibe. ๐ญ
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 least MinimumParameterNameLength (default is 3). Setting it to 0 or 1 disables the check entirely.
int doubler(int x) // warns that x is too short
{
return 2 * x;
}
Same deal as above: this check does not have any fix suggestions in the general case, because 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 โ the ancestors said so, we obey. ๐
MinimumLoopCounterNameLength
Loop counter variables are expected to have a length of at least MinimumLoopCounterNameLength characters (default is 2). Setting it to 0 or 1 disables 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 because it is 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 least MinimumExceptionNameLength (default is 2). Setting it to 0 or 1 disables the check entirely.
try {
// ...
}
// This warns that 'e' is too short.
catch (const std::exception& x) {
// ...
}
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. Basically it's the vibe check for your naming conventions. ๐
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.
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 โ so if you don't set the specific one, it inherits from the more general one. Lowkey elegant ngl.
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
okay so when you define this one, the check makes sure your abstract class names match whatever casing you picked. no cap
๐ AbstractClassPrefix
when this is defined, the check makes abstract class names prefixed with whatever value you give it (casing is not the vibe here, it just slaps the prefix on).
๐ฆ AbstractClassIgnoredRegexp
identifier naming checks straight up will not be enforced for abstract class names that match this regular expression. it's giving immunity
๐ธ AbstractClassSuffix
when defined, the check makes abstract class names add the suffix with the given value (again, regardless of casing).
๐ AbstractClassHungarianPrefix
when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type. iykyk
For example using values of:
AbstractClassCaseoflower_caseAbstractClassPrefixofpre_AbstractClassSuffixof_postAbstractClassHungarianPrefixofOn
Identifies and/or transforms abstract class names as follows:
Before:
class ABSTRACT_CLASS {
public:
ABSTRACT_CLASS();
};
After:
class pre_abstract_class_post {
public:
pre_abstract_class_post();
};
๐ฅ AggressiveDependentMemberLookup
when this is set to true, the check will go look in dependent base classes for dependent member references that need changing. fair warning bestie โ this can lead to errors with template specializations, so the default value is false.
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;
}
};
๐ซถ CheckAnonFieldInParent
when set to true, fields in anonymous records (like, anonymous unions and structs, you know the ones) get treated as names in the enclosing scope rather than public members of the anonymous record, for the purpose of name checking. subtle but iconic
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_ will be treated as private data members of Foo for the purpose of name checking, and thus no warnings will be emitted. problem solved, periodt
๐ ClassCase
when defined, the check makes sure class names conform to the casing you selected.
โจ ClassPrefix
when defined, the check makes class names add the prefix with the given value (regardless of casing, we said what we said).
๐ ClassIgnoredRegexp
identifier naming checks won't be enforced for class names matching this regular expression.
๐ ClassSuffix
when defined, the check makes class names add the suffix with the given value (regardless of casing).
๐ ClassHungarianPrefix
when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type.
For example using values of:
ClassCaseoflower_caseClassPrefixofpre_ClassSuffixof_postClassHungarianPrefixofOn
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();
};
๐ฆ ClassConstantCase
when defined, the check makes sure class constant names conform to the selected casing.
๐ ClassConstantPrefix
when defined, the check makes class constant names add the prefix with the given value (regardless of casing).
๐ ClassConstantIgnoredRegexp
identifier naming checks won't be enforced for class constant names matching this regular expression.
๐ธ ClassConstantSuffix
when defined, the check makes class constant names add the suffix with the given value (regardless of casing).
๐ ClassConstantHungarianPrefix
when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type.
For example using values of:
ClassConstantCaseoflower_caseClassConstantPrefixofpre_ClassConstantSuffixof_postClassConstantHungarianPrefixofOn
Identifies and/or transforms class constant names as follows:
Before:
class FOO {
public:
static const int CLASS_CONSTANT;
};
After:
class FOO {
public:
static const int pre_class_constant_post;
};
๐ ClassMemberCase
when defined, the check makes sure class member names conform to the selected casing.
๐ ClassMemberPrefix
when defined, the check makes class member names add the prefix with the given value (regardless of casing).
๐ง ClassMemberIgnoredRegexp
identifier naming checks won't be enforced for class member names matching this regular expression.
โญ ClassMemberSuffix
when defined, the check makes class member names add the suffix with the given value (regardless of casing).
๐ ClassMemberHungarianPrefix
when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type.
For example using values of:
ClassMemberCaseoflower_caseClassMemberPrefixofpre_ClassMemberSuffixof_postClassMemberHungarianPrefixofOn
Identifies and/or transforms class member names as follows:
Before:
class FOO {
public:
static int CLASS_CONSTANT;
};
After:
class FOO {
public:
static int pre_class_constant_post;
};
๐ฅ ClassMethodCase
when defined, the check makes sure class method names conform to the selected casing.
๐ ClassMethodPrefix
when defined, the check makes class method names add the prefix with the given value (regardless of casing).
๐ ClassMethodIgnoredRegexp
identifier naming checks won't be enforced for class method names matching this regular expression.
๐ฆ ClassMethodSuffix
when defined, the check makes class method names add the suffix with the given value (regardless of casing).
For example using values of:
ClassMethodCaseoflower_caseClassMethodPrefixofpre_ClassMethodSuffixof_post
Identifies and/or transforms class method names as follows:
Before:
class FOO {
public:
int CLASS_MEMBER();
};
After:
class FOO {
public:
int pre_class_member_post();
};
โจ ConceptCase
when defined, the check makes sure concept names conform to the selected casing.
๐ ConceptPrefix
when defined, the check makes concept names add the prefix with the given value (regardless of casing).
๐ ConceptIgnoredRegexp
identifier naming checks won't be enforced for concept names matching this regular expression.
๐ธ ConceptSuffix
when defined, the check makes concept names add the suffix with the given value (regardless of casing).
For example using values of:
ConceptCaseofCamelCaseConceptPrefixofPreConceptSuffixofPost
Identifies and/or transforms concept names as follows:
Before:
template<typename T> concept my_concept = requires (T t) { {t++}; };
After:
template<typename T> concept PreMyConceptPost = requires (T t) { {t++}; };
๐ ConstantCase
when defined, the check makes sure constant names conform to the selected casing.
โญ ConstantPrefix
when defined, the check makes constant names add the prefix with the given value (regardless of casing).
๐ง ConstantIgnoredRegexp
identifier naming checks won't be enforced for constant names matching this regular expression.
๐ ConstantSuffix
when defined, the check makes constant names add the suffix with the given value (regardless of casing).
๐ ConstantHungarianPrefix
when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type.
For example using values of:
ConstantCaseoflower_caseConstantPrefixofpre_ConstantSuffixof_postConstantHungarianPrefixofOn
Identifies and/or transforms constant names as follows:
Before:
void function() { unsigned const MyConst_array[] = {1, 2, 3}; }
After:
void function() { unsigned const pre_myconst_array_post[] = {1, 2, 3}; }
๐ธ ConstantMemberCase ๐ธ
When you set this, bestie, the check makes sure your constant member names match the casing you picked. No cap.
๐ฆ ConstantMemberPrefix ๐ฆ
When defined, the check will make your constant member names start with the prefix you give it (and it does this regardless of casing, okay? the casing rule stays separate).
๐ ConstantMemberIgnoredRegexp ๐
If a constant member name matches this regex, the identifier naming checks just... won't bother it. It's giving โจexemptโจ.
๐
ConstantMemberSuffix ๐
When defined, the check will make your constant member names end with the suffix you give it (again โ regardless of casing).
๐ ConstantMemberHungarianPrefix ๐
When you turn this on, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type. Very demure, very type-aware.
So like, for example, using values of:
ConstantMemberCaseoflower_caseConstantMemberPrefixofpre_ConstantMemberSuffixof_postConstantMemberHungarianPrefixofOn
It identifies and/or transforms constant member names as follows:
Before:
class Foo {
char const MY_ConstMember_string[4] = "123";
}
After:
class Foo {
char const pre_my_constmember_string_post[4] = "123";
}
๐ธ ConstantParameterCase ๐ธ
When defined, the check will ensure constant parameter names conform to the selected casing. slay.
๐ฆ ConstantParameterPrefix ๐ฆ
When defined, the check will ensure constant parameter names will add the prefixed with the given value (regardless of casing).
๐ ConstantParameterIgnoredRegexp ๐
Identifier naming checks won't be enforced for constant parameter names matching this regular expression.
๐
ConstantParameterSuffix ๐
When defined, the check will ensure constant parameter names will add the suffix with the given value (regardless of casing).
๐ ConstantParameterHungarianPrefix ๐
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
ConstantParameterCaseoflower_caseConstantParameterPrefixofpre_ConstantParameterSuffixof_postConstantParameterHungarianPrefixofOn
Identifies and/or transforms constant parameter names as follows:
Before:
void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);
After:
void GLOBAL_FUNCTION(int PARAMETER_1, int const pre_const_parameter_post);
๐ธ ConstantPointerParameterCase ๐ธ
When defined, the check will ensure constant pointer parameter names conform to the selected casing.
๐ฆ ConstantPointerParameterPrefix ๐ฆ
When defined, the check will ensure constant pointer parameter names will add the prefixed with the given value (regardless of casing).
๐ ConstantPointerParameterIgnoredRegexp ๐
Identifier naming checks won't be enforced for constant pointer parameter names matching this regular expression.
๐
ConstantPointerParameterSuffix ๐
When defined, the check will ensure constant pointer parameter names will add the suffix with the given value (regardless of casing).
๐ ConstantPointerParameterHungarianPrefix ๐
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
ConstantPointerParameterCaseoflower_caseConstantPointerParameterPrefixofpre_ConstantPointerParameterSuffixof_postConstantPointerParameterHungarianPrefixofOn
Identifies and/or transforms constant pointer parameter names as follows:
Before:
void GLOBAL_FUNCTION(int const *CONST_parameter);
After:
void GLOBAL_FUNCTION(int const *pre_const_parameter_post);
๐ธ ConstexprFunctionCase ๐ธ
When defined, the check will ensure constexpr function names conform to the selected casing.
๐ฆ ConstexprFunctionPrefix ๐ฆ
When defined, the check will ensure constexpr function names will add the prefixed with the given value (regardless of casing).
๐ ConstexprFunctionIgnoredRegexp ๐
Identifier naming checks won't be enforced for constexpr function names matching this regular expression.
๐
ConstexprFunctionSuffix ๐
When defined, the check will ensure constexpr function names will add the suffix with the given value (regardless of casing).
For example using values of:
ConstexprFunctionCaseoflower_caseConstexprFunctionPrefixofpre_ConstexprFunctionSuffixof_post
Identifies and/or transforms constexpr function names as follows:
Before:
constexpr int CE_function() { return 3; }
After:
constexpr int pre_ce_function_post() { return 3; }
๐ธ ConstexprMethodCase ๐ธ
When defined, the check will ensure constexpr method names conform to the selected casing.
๐ฆ ConstexprMethodPrefix ๐ฆ
When defined, the check will ensure constexpr method names will add the prefixed with the given value (regardless of casing).
๐ ConstexprMethodIgnoredRegexp ๐
Identifier naming checks won't be enforced for constexpr method names matching this regular expression.
๐
ConstexprMethodSuffix ๐
When defined, the check will ensure constexpr method names will add the suffix with the given value (regardless of casing).
For example using values of:
ConstexprMethodCaseoflower_caseConstexprMethodPrefixofpre_ConstexprMethodSuffixof_post
Identifies and/or transforms constexpr method names as follows:
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 will ensure constexpr variable names conform to the selected casing.
๐ฆ ConstexprVariablePrefix ๐ฆ
When defined, the check will ensure constexpr variable names will add the prefixed with the given value (regardless of casing).
๐ ConstexprVariableIgnoredRegexp ๐
Identifier naming checks won't be enforced for constexpr variable names matching this regular expression.
๐
ConstexprVariableSuffix ๐
When defined, the check will ensure constexpr variable names will add the suffix with the given value (regardless of casing).
๐ ConstexprVariableHungarianPrefix ๐
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
ConstexprVariableCaseoflower_caseConstexprVariablePrefixofpre_ConstexprVariableSuffixof_postConstexprVariableHungarianPrefixofOn
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 will ensure enumeration names conform to the selected casing.
๐ฆ EnumPrefix ๐ฆ
When defined, the check will ensure enumeration names will add the 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 will ensure enumeration names will add the suffix with the given value (regardless of casing).
For example using values of:
EnumCaseoflower_caseEnumPrefixofpre_EnumSuffixof_post
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 will ensure enumeration constant names conform to the selected casing.
๐ฆ EnumConstantPrefix ๐ฆ
When defined, the check will ensure enumeration constant names will add the 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 will ensure enumeration constant names will add the suffix with the given value (regardless of casing).
๐ EnumConstantHungarianPrefix ๐
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
EnumConstantCaseoflower_caseEnumConstantPrefixofpre_EnumConstantSuffixof_postEnumConstantHungarianPrefixofOn
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 will ensure function names conform to the selected casing.
๐ฆ FunctionPrefix ๐ฆ
When defined, the check will ensure function names will add the 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 will ensure function names will add the suffix with the given value (regardless of casing).
For example using values of:
FunctionCaseoflower_caseFunctionPrefixofpre_FunctionSuffixof_post
Identifies and/or transforms function names as follows:
Before:
char MY_Function_string();
After:
char pre_my_function_string_post();
๐ซถ GetConfigPerFile ๐ซถ
When true, the check will look for the configuration for where an identifier is declared. Super useful when your included header files are out here using a whole different style than the rest of your code, bestie. Default value is true.
๐ธ GlobalConstantCase ๐ธ
When defined, the check will ensure global constant names conform to the selected casing.
๐ฆ GlobalConstantPrefix ๐ฆ
When defined, the check will ensure global constant names will add the 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 you define this one, the check makes sure global constant names get that suffix tacked on with the value you gave it (casing? don't care, it's added regardless).
GlobalConstantHungarianPrefix โจ
Turn this on and the check makes sure the identifier you declared rocks a Hungarian notation prefix based on the declared type. iykyk.
So like, imagine you set these values:
GlobalConstantCaseoflower_caseGlobalConstantPrefixofpre_GlobalConstantSuffixof_postGlobalConstantHungarianPrefixofOn
Then it spots and/or transforms global constant names like this:
Before:
unsigned const MyConstGlobal_array[] = {1, 2, 3};
After:
unsigned const pre_myconstglobal_array_post[] = {1, 2, 3};
GlobalConstantPointerCase ๐
Define it and the check will make sure global constant pointer names conform to whatever casing you picked.
GlobalConstantPointerPrefix ๐ธ
Define it and the check will make sure global constant pointer names get prefixed with the value you gave (regardless of casing, bestie).
GlobalConstantPointerIgnoredRegexp ๐ฆ
Identifier naming checks won't be enforced for global constant pointer names that match this regular expression. Like, they're exempt. periodt.
GlobalConstantPointerSuffix ๐
Define it and the check will make sure global constant pointer names get the suffix with the given value (regardless of casing).
GlobalConstantPointerHungarianPrefix ๐
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
GlobalConstantPointerCaseoflower_caseGlobalConstantPointerPrefixofpre_GlobalConstantPointerSuffixof_postGlobalConstantPointerHungarianPrefixofOn
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 will ensure global function names conform to the selected casing.
GlobalFunctionPrefix ๐
When defined, the check will ensure global function names will add the 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 will ensure global function names will add the suffix with the given value (regardless of casing).
For example using values of:
GlobalFunctionCaseoflower_caseGlobalFunctionPrefixofpre_GlobalFunctionSuffixof_post
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 will ensure global pointer names conform to the selected casing.
GlobalPointerPrefix ๐ซถ
When defined, the check will ensure global pointer names will add the 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 will ensure global pointer names will add the suffix with the given value (regardless of casing).
GlobalPointerHungarianPrefix โจ
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
GlobalPointerCaseoflower_caseGlobalPointerPrefixofpre_GlobalPointerSuffixof_postGlobalPointerHungarianPrefixofOn
Identifies and/or transforms global pointer names as follows:
Before:
int *GLOBAL3;
After:
int *pre_global3_post;
GlobalVariableCase ๐
When defined, the check will ensure global variable names conform to the selected casing.
GlobalVariablePrefix ๐ธ
When defined, the check will ensure global variable names will add the 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 will ensure global variable names will add the suffix with the given value (regardless of casing).
GlobalVariableHungarianPrefix ๐
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
GlobalVariableCaseoflower_caseGlobalVariablePrefixofpre_GlobalVariableSuffixof_postGlobalVariableHungarianPrefixofOn
Identifies and/or transforms global variable names as follows:
Before:
int GLOBAL3;
After:
int pre_global3_post;
IgnoreMainLikeFunctions โญ
When set to true functions that have a similar signature to main or wmain won't enforce checks on the names of their parameters. Default value is false.
InlineNamespaceCase ๐
When defined, the check will ensure inline namespaces names conform to the selected casing.
InlineNamespacePrefix ๐
When defined, the check will ensure inline namespaces names will add the prefixed with the given value (regardless of casing).
InlineNamespaceIgnoredRegexp ๐
Identifier naming checks won't be enforced for inline namespaces names matching this regular expression.
InlineNamespaceSuffix ๐ง
When defined, the check will ensure inline namespaces names will add the suffix with the given value (regardless of casing).
For example using values of:
InlineNamespaceCaseoflower_caseInlineNamespacePrefixofpre_InlineNamespaceSuffixof_post
Identifies and/or transforms inline namespaces names as follows:
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 will ensure local constant names conform to the selected casing.
LocalConstantPrefix ๐ฅ
When defined, the check will ensure local constant names will add the 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 will ensure local constant names will add the suffix with the given value (regardless of casing).
LocalConstantHungarianPrefix ๐
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
LocalConstantCaseoflower_caseLocalConstantPrefixofpre_LocalConstantSuffixof_postLocalConstantHungarianPrefixofOn
Identifies and/or transforms local constant names as follows:
Before:
void foo() { int const local_Constant = 3; }
After:
void foo() { int const pre_local_constant_post = 3; }
LocalConstantPointerCase ๐ธ
When defined, the check will ensure local constant pointer names conform to the selected casing.
LocalConstantPointerPrefix ๐ฆ
When defined, the check will ensure local constant pointer names will add the 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 will ensure local constant pointer names will add the suffix with the given value (regardless of casing).
LocalConstantPointerHungarianPrefix โญ
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
LocalConstantPointerCaseoflower_caseLocalConstantPointerPrefixofpre_LocalConstantPointerSuffixof_postLocalConstantPointerHungarianPrefixofOn
Identifies and/or transforms local constant pointer names as follows:
Before:
void foo() { int const *local_Constant = 3; }
After:
void foo() { int const *pre_local_constant_post = 3; }
LocalPointerCase ๐
When defined, the check will ensure local pointer names conform to the selected casing.
LocalPointerPrefix ๐
When defined, the check will ensure local pointer names will add the 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 will ensure local pointer names will add the suffix with the given value (regardless of casing).
LocalPointerHungarianPrefix ๐ซถ
When enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
LocalPointerCaseoflower_caseLocalPointerPrefixofpre_LocalPointerSuffixof_postLocalPointerHungarianPrefixofOn
Identifies and/or transforms local pointer names as follows:
Before:
void foo() { int *local_Constant; }
After:
void foo() { int *pre_local_constant_post; }
LocalVariableCase ๐ฅ
When defined, the check will ensure local variable names conform to the selected casing.
LocalVariablePrefix ๐
When defined, the check will ensure local variable names will add the 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}
Will exclude variables with a length less than or equal to 2 from the camel case check applied to other variables. Yeah bestie โ tiny names get a pass on the camelCase vibe check, everyone else gets judged ๐
LocalVariableSuffix
When you set this, the check makes sure local variable names get that suffix tacked on with the value you gave it (no matter what casing, it just slaps it on).
LocalVariableHungarianPrefix
Turn this on and the check makes sure the declared identifier gets a Hungarian notation prefix based on whatever type it's declared as. Yes, the classic hungarian moment, we're bringing it back ๐ซถ
For example using values of:
LocalVariableCaseoflower_caseLocalVariablePrefixofpre_LocalVariableSuffixof_postLocalVariableHungarianPrefixofOn
Identifies and/or transforms local variable names as follows:
Before:
void foo() { int local_Constant; }
After:
void foo() { int pre_local_constant_post; }
MacroDefinitionCase
When defined, the check will make sure macro definitions conform to the casing you picked. โจ
MacroDefinitionPrefix
When defined, the check will make sure 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. So if it matches, it's off the hook, no questions asked.
MacroDefinitionSuffix
When defined, the check will make sure macro definitions get the suffix with the given value (regardless of casing).
For example using values of:
MacroDefinitionCaseoflower_caseMacroDefinitionPrefixofpre_MacroDefinitionSuffixof_post
Identifies and/or transforms macro definitions as follows:
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. Those ones are literally built different and get left alone ๐
MemberCase
When defined, the check will make sure member names conform to the selected casing.
MemberPrefix
When defined, the check will make sure 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 will make sure member names get the suffix with the given value (regardless of casing).
MemberHungarianPrefix
When enabled, the check makes sure the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
MemberCaseoflower_caseMemberPrefixofpre_MemberSuffixof_postMemberHungarianPrefixofOn
Identifies and/or transforms member names as follows:
Before:
class Foo {
char MY_ConstMember_string[4];
}
After:
class Foo {
char pre_my_constmember_string_post[4];
}
MethodCase
When defined, the check will make sure method names conform to the selected casing.
MethodPrefix
When defined, the check will make sure 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 will make sure method names get the suffix with the given value (regardless of casing).
For example using values of:
MethodCaseoflower_caseMethodPrefixofpre_MethodSuffixof_post
Identifies and/or transforms method names as follows:
Before:
class Foo {
char MY_Method_string();
}
After:
class Foo {
char pre_my_method_string_post();
}
NamespaceCase
When defined, the check will make sure namespace names conform to the selected casing.
NamespacePrefix
When defined, the check will make sure 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 will make sure namespace names get the suffix with the given value (regardless of casing).
For example using values of:
NamespaceCaseoflower_caseNamespacePrefixofpre_NamespaceSuffixof_post
Identifies and/or transforms namespace names as follows:
Before:
namespace FOO_NS {
...
}
After:
namespace pre_foo_ns_post {
...
}
ParameterCase
When defined, the check will make sure parameter names conform to the selected casing.
ParameterPrefix
When defined, the check will make sure 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 defined, the check will make sure parameter names get the suffix with the given value (regardless of casing).
ParameterHungarianPrefix
When enabled, the check makes sure the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
ParameterCaseoflower_caseParameterPrefixofpre_ParameterSuffixof_postParameterHungarianPrefixofOn
Identifies and/or transforms parameter names as follows:
Before:
void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);
After:
void GLOBAL_FUNCTION(int pre_parameter_post, int const CONST_parameter);
ParameterPackCase
When defined, the check will make sure parameter pack names conform to the selected casing.
ParameterPackPrefix
When defined, the check will make 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.
ParameterPackSuffix
When defined, the check will make sure parameter pack names get the suffix with the given value (regardless of casing).
For example using values of:
ParameterPackCaseoflower_caseParameterPackPrefixofpre_ParameterPackSuffixof_post
Identifies and/or transforms parameter pack names as follows:
Before:
template <typename... TYPE_parameters> {
void FUNCTION(int... TYPE_parameters);
}
After:
template <typename... TYPE_parameters> {
void FUNCTION(int... pre_type_parameters_post);
}
PointerParameterCase
When defined, the check will make sure pointer parameter names conform to the selected casing.
PointerParameterPrefix
When defined, the check will make 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.
PointerParameterSuffix
When defined, the check will make sure pointer parameter names get the suffix with the given value (regardless of casing).
PointerParameterHungarianPrefix
When enabled, the check makes sure the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
PointerParameterCaseoflower_casePointerParameterPrefixofpre_PointerParameterSuffixof_postPointerParameterHungarianPrefixofOn
Identifies and/or transforms pointer parameter names as follows:
Before:
void FUNCTION(int *PARAMETER);
After:
void FUNCTION(int *pre_parameter_post);
PrivateMemberCase
When defined, the check will make sure private member names conform to the selected casing.
PrivateMemberPrefix
When defined, the check will make 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 will make sure private member names get the suffix with the given value (regardless of casing).
PrivateMemberHungarianPrefix
When enabled, the check makes sure the declared identifier will have a Hungarian notation prefix based on the declared type.
For example using values of:
PrivateMemberCaseoflower_casePrivateMemberPrefixofpre_PrivateMemberSuffixof_postPrivateMemberHungarianPrefixofOn
Identifies and/or transforms private member names as follows:
Before:
class Foo {
private:
int Member_Variable;
}
After:
class Foo {
private:
int pre_member_variable_post;
}
PrivateMethodCase
When defined, the check will make sure private method names conform to the selected casing.
PrivateMethodPrefix
When defined, the check will make 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 will make sure private method names get the suffix with the given value (regardless of casing).
For example using values of:
PrivateMethodCaseoflower_casePrivateMethodPrefixofpre_PrivateMethodSuffixof_post
Identifies and/or transforms private method names as follows:
Before:
class Foo {
private:
int Member_Method();
}
After:
class Foo {
private:
int pre_member_method_post();
}
ProtectedMemberCase
When defined, the check will make sure protected member names conform to the selected casing.
ProtectedMemberPrefix
When defined, the check will make sure protected member names get prefixed with the given value (regardless of casing).
ProtectedMemberIgnoredRegexpโ if a protected member name matches this regex, the naming checks justโฆ don't bother with it. It's exempt, bestie ๐ProtectedMemberSuffixโ set this and the check makes sure protected member names get the given suffix tacked on (casing is not the vibe here, it just appends).ProtectedMemberHungarianPrefixโ turn this on and the check makes sure the declared identifier gets a Hungarian notation prefix based on its declared type. iykyk.
So 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 casing you picked โจProtectedMethodPrefixโ when defined, the check makes sure protected method names get the given prefix added (regardless of casing, it just slaps it on the front).ProtectedMethodIgnoredRegexpโ protected method names matching this regex are skipped by the identifier naming checks. Not today, check. ๐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 (yes, the original says "protect method names", iconic typo, we respect it ๐):
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 the given prefix added (regardless of casing).PublicMemberIgnoredRegexpโ public member names matching this regex get left alone by the identifier naming checks. exempt, periodt.PublicMemberSuffixโ when defined, the check makes sure public member names get the given suffix added (regardless of casing).PublicMemberHungarianPrefixโ turn it on and the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type. fr fr.
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;
}
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 the given prefix added (regardless of casing).PublicMethodIgnoredRegexpโ public method names matching this regex are exempt from the identifier naming checks. no notes.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 the given prefix added (regardless of casing).ScopedEnumConstantIgnoredRegexpโ scoped enum constant names matching this regex skip the identifier naming checks entirely ๐ScopedEnumConstantSuffixโ when defined, the check makes sure scoped enum constant names get the given suffix added (regardless of casing).ScopedEnumConstantHungarianPrefixโ turn it on and the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type. obsessed.
For example using values of:
ScopedEnumConstantCaseoflower_caseScopedEnumConstantPrefixofpre_ScopedEnumConstantSuffixof_postScopedEnumConstantHungarianPrefixofOn
It identifies and/or transforms enumeration constant names like this:
Before:
enum class FOO { One, Two, Three };
After:
enum class FOO { pre_One_post, pre_Two_post, pre_Three_post };
StaticConstantCaseโ when defined, the check makes sure static constant names conform to the selected casing.StaticConstantPrefixโ when defined, the check makes sure static constant names get the given prefix added (regardless of casing).StaticConstantIgnoredRegexpโ static constant names matching this regex are not enforced against by the identifier naming checks ๐StaticConstantSuffixโ when defined, the check makes sure static constant names get the given suffix added (regardless of casing).StaticConstantHungarianPrefixโ turn it on and the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type.
For example using values of:
StaticConstantCaseoflower_caseStaticConstantPrefixofpre_StaticConstantSuffixof_postStaticConstantHungarianPrefixofOn
It identifies and/or transforms static constant names like this:
Before:
static unsigned const MyConstStatic_array[] = {1, 2, 3};
After:
static unsigned const pre_myconststatic_array_post[] = {1, 2, 3};
StaticVariableCaseโ when defined, the check makes sure static variable names conform to the selected casing.StaticVariablePrefixโ when defined, the check makes sure static variable names get the given prefix added (regardless of casing).StaticVariableIgnoredRegexpโ static variable names matching this regex are left out of the identifier naming checks โจStaticVariableSuffixโ when defined, the check makes sure static variable names get the given suffix added (regardless of casing).StaticVariableHungarianPrefixโ turn it on and the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type.
For example using values of:
StaticVariableCaseoflower_caseStaticVariablePrefixofpre_StaticVariableSuffixof_postStaticVariableHungarianPrefixofOn
It identifies and/or transforms static variable names like this:
Before:
static unsigned MyStatic_array[] = {1, 2, 3};
After:
static unsigned pre_mystatic_array_post[] = {1, 2, 3};
StructCaseโ when defined, the check makes sure struct names conform to the selected casing.StructPrefixโ when defined, the check makes sure struct names get the given prefix added (regardless of casing).StructIgnoredRegexpโ struct names matching this regex are exempt from the identifier naming checks ๐ฆStructSuffixโ when defined, the check makes sure struct names get the given suffix added (regardless of casing).
For example using values of:
StructCaseoflower_caseStructPrefixofpre_StructSuffixof_post
It identifies and/or transforms struct names like this:
Before:
struct FOO {
FOO();
~FOO();
};
After:
struct pre_foo_post {
pre_foo_post();
~pre_foo_post();
};
TemplateParameterCaseโ when defined, the check makes sure template parameter names conform to the selected casing.TemplateParameterPrefixโ when defined, the check makes sure template parameter names get the given prefix added (regardless of casing).TemplateParameterIgnoredRegexpโ template parameter names matching this regex are not enforced against by the identifier naming checks ๐TemplateParameterSuffixโ when defined, the check makes sure template parameter names get the given suffix added (regardless of casing).
For example using values of:
TemplateParameterCaseoflower_caseTemplateParameterPrefixofpre_TemplateParameterSuffixof_post
It identifies and/or transforms template parameter names like this:
Before:
template <typename T> class Foo {};
After:
template <typename pre_t_post> class Foo {};
TemplateTemplateParameterCaseโ when defined, the check makes sure template template parameter names conform to the selected casing.TemplateTemplateParameterPrefixโ when defined, the check makes sure template template parameter names get the given prefix added (regardless of casing).TemplateTemplateParameterIgnoredRegexpโ template template parameter names matching this regex are exempt from the identifier naming checks ๐TemplateTemplateParameterSuffixโ when defined, the check makes sure template template parameter names get the given suffix added (regardless of casing).
For example using values of:
TemplateTemplateParameterCaseoflower_caseTemplateTemplateParameterPrefixofpre_TemplateTemplateParameterSuffixof_post
It identifies and/or transforms template template parameter names like this:
Before:
template <template <typename> class TPL_parameter, int COUNT_params,
typename... TYPE_parameters>
and then afterwards it's giving:
template <template <typename> class pre_tpl_parameter_post, int COUNT_params,
typename... TYPE_parameters>
TypeAliasCase</p> ok so when this one's defined, the check makes sure your type alias names match whatever casing you picked. simple as that ๐
TypeAliasPrefix</p> when defined, the check makes type alias names get prefixed with the value you gave it โ and yes, that happens regardless of casing, no cap.
TypeAliasIgnoredRegexp</p> if a type alias name matches this regular expression, the identifier naming checks just... won't be enforced on it. it's giving "exempt, bestie" ๐
TypeAliasSuffix</p> when defined, the check makes type alias names get the suffix you gave it tacked on โ again, regardless of casing.
so for example, if you're running with values of:
TypeAliasCaseoflower_caseTypeAliasPrefixofpre_TypeAliasSuffixof_post
then it identifies and/or transforms type alias names like this:
Before:
using MY_STRUCT_TYPE = my_structure;
After:
using pre_my_struct_type_post = my_structure;
TypedefCase</p> when defined, the check will ensure typedef names conform to the selected casing. slay ๐
TypedefPrefix</p> when defined, the check will ensure typedef names will add the prefixed with the given value (regardless of casing).
TypedefIgnoredRegexp</p> identifier naming checks won't be enforced for typedef names matching this regular expression.
TypedefSuffix</p> when defined, the check will ensure typedef names will add the suffix with the given value (regardless of casing).
for example using values of:
TypedefCaseoflower_caseTypedefPrefixofpre_TypedefSuffixof_post
it identifies and/or transforms typedef names as follows:
Before:
typedef int MYINT;
After:
typedef int pre_myint_post;
TypeTemplateParameterCase</p> when defined, the check will ensure type template parameter names conform to the selected casing.
TypeTemplateParameterPrefix</p> when defined, the check will ensure type template parameter names will add the prefixed with the given value (regardless of casing).
TypeTemplateParameterIgnoredRegexp</p> identifier naming checks won't be enforced for type template names matching this regular expression.
TypeTemplateParameterSuffix</p> when defined, the check will ensure type template parameter names will add the suffix with the given value (regardless of casing).
for example using values of:
TypeTemplateParameterCaseoflower_caseTypeTemplateParameterPrefixofpre_TypeTemplateParameterSuffixof_post
it 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</p> when defined, the check will ensure union names conform to the selected casing.
UnionPrefix</p> when defined, the check will ensure union names will add the prefixed with the given value (regardless of casing).
UnionIgnoredRegexp</p> identifier naming checks won't be enforced for union names matching this regular expression.
UnionSuffix</p> when defined, the check will ensure union names will add the suffix with the given value (regardless of casing).
for example using values of:
UnionCaseoflower_caseUnionPrefixofpre_UnionSuffixof_post
it 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</p> when defined, the check will ensure value template parameter names conform to the selected casing.
ValueTemplateParameterPrefix</p> when defined, the check will ensure value template parameter names will add the prefixed with the given value (regardless of casing).
ValueTemplateParameterIgnoredRegexp</p> identifier naming checks won't be enforced for value template parameter names matching this regular expression.
ValueTemplateParameterSuffix</p> when defined, the check will ensure value template parameter names will add the suffix with the given value (regardless of casing).
for example using values of:
ValueTemplateParameterCaseoflower_caseValueTemplateParameterPrefixofpre_ValueTemplateParameterSuffixof_post
it 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</p> when defined, the check will ensure variable names conform to the selected casing.
VariablePrefix</p> when defined, the check will ensure variable names will add the prefixed with the given value (regardless of casing).
VariableIgnoredRegexp</p> identifier naming checks won't be enforced for variable names matching this regular expression.
VariableSuffix</p> when defined, the check will ensure variable names will add the suffix with the given value (regardless of casing).
VariableHungarianPrefix</p> when enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type. bestie this one is so extra and I respect it ๐ซถ
for example using values of:
VariableCaseoflower_caseVariablePrefixofpre_VariableSuffixof_postVariableHungarianPrefixofOn
it identifies and/or transforms variable names as follows:
Before:
unsigned MyVariable;
After:
unsigned pre_myvariable_post;
VirtualMethodCase</p> when defined, the check will ensure virtual method names conform to the selected casing.
VirtualMethodPrefix</p> when defined, the check will ensure virtual method names will add the prefixed with the given value (regardless of casing).
VirtualMethodIgnoredRegexp</p> identifier naming checks won't be enforced for virtual method names matching this regular expression.
VirtualMethodSuffix</p> when defined, the check will ensure virtual method names will add the suffix with the given value (regardless of casing).
for example using values of:
VirtualMethodCaseoflower_caseVirtualMethodPrefixofpre_VirtualMethodSuffixof_post
it identifies and/or transforms virtual method names as follows:
Before:
class Foo {
public:
virtual int MemberFunction();
}
After:
class Foo {
public:
virtual int pre_member_function_post();
}
๐ The default mapping table of Hungarian Notation ๐
ok so 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. the first character of the given name can be capitalized to separate it from the type indicators (see also CamelCase). otherwise the case of this character denotes scope. no cap, that's literally how it works ๐
so bestie, here's the whole vibe of this thing ๐ this is the default mapping table of Hungarian Notation, and what it does is map Decl to its prefix string. and ngl, you can also have your own style in a config file โ your rules, your world ๐
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
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
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 more trivial options for Hungarian Notation too, so don't clock out yet ๐ธ here's the little family tree of them:
HungarianNotation.General.*โ these options are not belonging to any specificDecl. they're the free spirits, noDeclto call home ๐ฆHungarianNotation.CString.*โ options for NULL-terminated string. yes, thatNULL-terminated kind ๐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.TreatStructAsClass
and that's where this part cuts off, bestie โ more coming, stay tuned ๐
HungarianNotation.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</p>
bestie okay so when this one is defined, the check literally treats the naming of a struct as if it were a class. that's the whole vibe. the default value is false. ๐
HungarianNotation.DerivedType.Array</p>
when it's defined, the check makes sure the variable name gets the prefix tacked on โ the given string, yk? the default prefix is a. ๐
HungarianNotation.DerivedType.Pointer</p>
when it's defined, the check makes sure the variable name gets the prefix added with the given string. default prefix is p. โจ
HungarianNotation.DerivedType.FunctionPointer</p>
when it's 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</p>
when it's defined, the check makes sure the variable name adds the prefix with the given string. default prefix is sz. ๐ฆ
HungarianNotation.CString.CharArray</p>
when it's defined, the check makes sure the variable name adds the prefix with the given string. default prefix is sz. ๐ฆ
HungarianNotation.CString.WideCharPointer</p>
when it's defined, the check makes sure the variable name adds the prefix with the given string. default prefix is wsz. ๐
HungarianNotation.CString.WideCharArray</p>
when it's 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.*</p>
when it's 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. no cap. โญ
HungarianNotation.UserDefinedType.*</p>
when it's 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. ๐
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 so plot twist: this check got renamed to readability-implicit-bool-conversion. that's it, that's the whole tea. ๐
readability-implicit-bool-conversion
bestie, this check is the one that sniffs out implicit conversions between built-in types and booleans. depending on ur use case it might just be a lil readability glow-up, or it might be pointing at actual potential bugs that are out here lurking unnoticed because implicit conversions said "trust me bro". ๐ญ
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 leftover code is no longer correct โ yet it still compiles with zero visible warnings. it's giving "it's fine" while it is absolutely not fine. ๐
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, for example:
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/NULLto 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 to make it 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
boolto other built-in types, an explicitstatic_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, as it is not clear how to deal with such cases. like, nobody knows what to do there, so the check just respectfully looks away. ๐ซถ
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
this one finds function declarations that differ in parameter names. it's giving detective energy. ๐
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 to enforce consistency in large projects, where it often happens that a definition of a function gets refactored, changing the parameter names, but its declaration in the header file is never updated. with this check, we can easily find and correct such inconsistencies, keeping declaration and definition always in sync. slay. โจ
unnamed parameters are allowed and are not taken into account when comparing function declarations, for example:
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, as it directly references parameter names in its body. main character energy, honestly. 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. yes, every single one. ๐
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 vibes, no flexibility, just exact. periodt. ๐
๐ธ readability-isolate-declaration ๐ธ
okay bestie, so this check is lowkey a glow-up artist for your declarations. It sniffs out local variable declarations that are trying to do too much โ like, declaring more than one variable in a single statement โ and it tries to refactor the code so you get one statement per declaration. periodt. ๐
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. slay. โจ
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;
}
Now โ the check excludes places where it's necessary or common to declare multiple variables in one statement and there's genuinely no other way supported in the language. also heads up: structured bindings are not considered. ๐
// 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. ๐ซ
The check currently does not support the automatic transformation of member-pointer-types.
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 โ like, it's giving paranoid-bestie energy โ 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. ๐
#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. iykyk, those sneaky little numbers just vibing in your source with zero explanation. ๐
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
- Rule 5.1.1 Use symbolic names instead of literal values in code in High Integrity C++
- 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.
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, amirite)
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 behaviour tbh. ๐
๐ 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 in typedef or using declarations. 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. main character energy for your const-correctness era. ๐
This check tries to annotate methods according to logical constness (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 though 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. ๐ซ
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 never-ending glow-up, fr fr. โจ
๐งฎ readability-math-missing-parentheses ๐งฎ
Check for missing parentheses in mathematical expressions that involve operators of different priorities. bestie, if you have to squint to figure out the precedence, this check is gonna have opinions. ๐ซถ
Okay so, parentheses in mathematical expressions? They're the little โจvibe checkโจ on your order of operations, especially when different-priority operators are all fighting for the aux. When expressions get long or span multiple lines, that order gets mad murky and boom, coding errors ๐. IDEs can help out by highlighting your parens, and honestly just writing them in on purpose also shows the world what the developer meant by that expression. Keeping them present = less ambiguity, fewer errors, more readable and maintainable code. Periodt. ๐
Before:
int x = 1 + 2 * 3 - 4 / 5;
After:
int x = 1 + (2 * 3) - (4 / 5);
readability-misleading-indentation
Bestie, correct indentation is literally how we read code. ๐ซถ When the syntactical structure and the indentation of the code don't match up, serious problems can be hiding right there in plain sight ๐ญ. And missing braces make the code significantly harder to read on top of that, so likeโฆ use the braces. Please.
The way to dodge the dangling else is to always check that an else belongs to the if that begins in the same column.
You can omit braces when the 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 as 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
Heads up: this check only works as expected when tabs or spaces are used consistently and not mixed. ๐ธ
readability-misplaced-array-index
This check is out here warning you about unusual array index syntax. ๐
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, no cap:
- 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.
readability-non-const-parameter
This 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 get 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. This 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 one. You need to explicitly configure it, bestie. ๐
To configure check to enforce Traditional Token Representation for all operators set options to &&;&=;&;|;~;!;!=;||;|=;^;^=.
To configure 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 advises to make it obvious if a 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 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.
readability-redundant-access-specifiers
Finds classes, structs, and unions containing redundant member (field and method) access specifiers. ๐
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. Slay. ๐
Options ๐
CheckFirstDeclarationโ if you set this totrue, bestie, the check is also gonna drag the very first access specifier in a class if it's being extra and redundant โ likeprivatesitting inside aclass, orpublicchillin' inside astructorunionwhere it's already the default. Default?false. So by default it minds its business ๐ซถ
Example โจ
struct Bar {
public:
int x;
}
If you flip CheckFirstDeclaration on, it will pop off with a warning about that redundant access specifier, because public is literally the default member access for structs. It's giving "why are you announcing yourself when everyone already knows" ๐ญ
readability-redundant-casting ๐
Okay so this one catches explicit type casts where the source type and destination type are the same, and then it's like "bestieโฆ why" and recommends you delete the whole thing. It covers a whole range: static_cast, const_cast, C-style casts, and reinterpret_cast. Its whole goal is to make your code more readable and maintainable by yeeting unnecessary type casting into the sun ๐
int value = 42;
int result = static_cast<int>(value);
See how static_cast<int>(value) is doing the most? It's casting an int to another int. Ma'am. That's not a glow-up, that's a no-op ๐
Things this check literally ignores, no cap:
- 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
- Bitfield-related casts
- Casts directly from lvalue to rvalue
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; when set totrue, it will resolve all type aliases and operate on the underlying types. Default isfalse.
readability-redundant-control-flow ๐
This check is on the hunt for procedures (aka functions that return no value) that have return statements just sitting there at the end of the function. Bestie, those returns are redundant. Periodt.
It also checks loop statements (for, while, do while) for redundant continue statements hanging out at the end of the loop body.
Examples, come look ๐ฆ
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) {
}
}
Look at that glow-down of a continue. Slay ๐
readability-redundant-declaration ๐ธ
Finds redundant variable and function declarations. We love a duplicate-detector.
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 leftovers from previous refactorings when code got moved around. And lowkey? Having redundant declarations could in the worst case mean there are typos in the code that cause bugs. So likeโฆ clean it up bestie ๐
Normally the code can be automatically fixed โ clang-tidy can remove the second declaration. BUT there are 2 cases where you need to fix the code manually:
- When the declarations are in different header files;
- When multiple variables are declared together.
Not me having to do manual labor in 2 whole scenarios ๐ญ the auto-fix can't save you there.
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. Extra *s? Gone. Not needed. Bye.
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 * was doing absolutely nothing. It was just there for the vibes. And the vibes were wrong ๐
readability-redundant-inline-specifier ๐
Detects redundant inline specifiers on function and variable declarations. If it's already implicitly inline, saying inline is justโฆ talking to talk.
Examples:
constexpr inline void f() {}
In the example above the keyword inline is redundant since constexpr functions are implicitly inlined. It's giving redundant bestie energy.
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. No need to announce it, queen, we already know ๐
Options ๐ฅ
StrictModeโ if set totrue, the check will also flag functions and variables that already have internal linkage as redundant.
readability-redundant-member-init ๐
Finds member initializations that are unnecessary because the exact same default constructor would be called if they weren't there. So like, you're typing extra for no reason, bestie.
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;
};
Compilers really said "initialize your bases or catch these warnings" and honestly? Fair. That's why this option exists ๐ซถ
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 conditions checking the same thing? That's so redundant it hurts ๐ญ the outer one already handled it bestie.
readability-redundant-smartptr-get ๐
Find and remove redundant calls to a smart pointer's .get() method. .get() when you don't need it is just extra steps for no reason.
Examples:
ptr.get()->Foo() ==> ptr->Foo()
*ptr.get() ==> *ptr
*ptr->get() ==> **ptr
if (ptr.get() == nullptr) ... => if (ptr == nullptr) ...
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(). If you didn't need to call it, don't call it. Simple as.
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. Empty string literal when the default already does that? Redundant, bestie.
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. Yeah. That's a yikes.
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.
Examples of problematic code include:
const std::string& str("hello");
struct Point { int x; int y; };
const Point& p = { 1, 2 };
In the first example, a const std::string& reference variable str is assigned a temporary object created by the std::string("hello") constructor. In the second example, a const Point& reference variable p is assigned an object that is 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 can make it difficult to reason about and may lead to subtle bugs or misunderstanding.
To avoid these issues, it is recommended to change the reference variable to a (const) value variable.
This is the kind of code that looks fine, compiles fine, and then haunts you at 2am. Change it to a value variable, bestie, your future self will thank you ๐ญ
readability-simplify-boolean-expr ๐ฅ
Looks for boolean expressions involving boolean constants and simplifies them to use the appropriate boolean expression directly. It also simplifies boolean expressions by application of DeMorgan's Theorem.
Boolean logic but make it โจcleanโจ. No more x == true energy. We're doing the most with the least, and that's iconic.
โจ Examples โจ
okay so here's the glow-up chart, bestie โ left column is what you wrote, right column is what you get after the fixer does its thing ๐
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:
- Unnecessary parentheses around the expression are removed. (yes, the extra little parentheses that were just there for vibes โ gone ๐ซถ)
- 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 ๐ธ
- 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 instancexof structX, the conditional returnif (x) return true; return false;becomesreturn static_cast<bool>(x);
๐ Options ๐
IgnoreMacrosโ iftrue, ignore boolean expressions that came from expanded macros. Default isfalse.ChainedConditionalReturnโ iftrue, conditional boolean return statements sitting at the end of an if/else if chain will be transformed. Default isfalse.ChainedConditionalAssignmentโ iftrue, conditional boolean assignments sitting at the end of an if/else if chain 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. Currently this covers calling .data() and immediately doing an array subscript operation to obtain a single element โ and in that case, simply calling operator[] suffice. ๐ like why are you doing two things when one thing does the job
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, static members are not yours to reach through an object, bestie ๐ญ
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.
โ ๏ธ heads up: --fix-notes can swap out expressions that have side effects, which could actually change how your program behaves. not a vibe you want by accident ๐
๐ 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. it's giving duplicate effort, honestly
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 ๐
Finds string comparisons using the compare method.
A common mistake is to use the string's compare method instead of using the equality or inequality operators. The compare method is intended for sorting functions and thus returns a negative number, a positive number or zero depending on the lexicographical relationship between the strings compared. If an equality or inequality check can suffice, that is recommended. This is recommended to avoid the risk of incorrect interpretation of the return value and to simplify the code. The string equality and inequality operators can also be faster than the compare method due to early termination. โญ
๐ 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) { }
<pink>Okay so</pink> those code examples up there? That's literally the whole list of if-statements this check will side-eye you about. Every single one of them is using `compare` to do an equality or inequality check on two strings instead of justโฆ using the right operators. <purple>It's giving "I forgot `==` exists" energy.</purple> ๐ญ
---
### โจ Options โจ
- **`StringLikeClasses`** โ a string with semicolon-separated names of string-like classes. By default it holds only `::std::basic_string` and `::std::basic_string_view`. If a class from this list has a `compare` method similar to `std::string`'s, it gets checked the exact same way. <cyan>No class left behind bestie ๐
</cyan>
### ๐ธ 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. <mint>slay, custom types get the same treatment ๐</mint>
---
## ๐ `readability-suspicious-call-argument` ๐
<pink>okay this one is genuinely iconic.</pink> It finds function calls where the arguments you passed are <hotpink>out of order</hotpink>, based on how different the argument name is from the parameter names of the function.
So like, given a function call `f(foo, bar);` and a function signature `void f(T tvar, U uvar)`, the arguments `foo` and `bar` are considered swapped if `foo` (the argument name) is <purple>more similar</purple> to `uvar` (the *other* parameter) than to `tvar` (the parameter it's actually being passed to) โ and `bar` is more similar to `tvar` than to `uvar`.
> Warnings might mean the arguments are actually swappedโฆ or just that the cross-similarity of the names is making your code harder to read. <yellow>Either way, a warning is a warning ๐</yellow>
### ๐ Heuristics ๐
These are the heuristics implemented in the check. If <hotpink>any</hotpink> of the enabled heuristics decides the arguments are out of order, you get a warning. <rainbow>democracy of vibes</rainbow> โจ
The heuristics themselves work by looking at pairs of strings, and they're <cyan>symmetric</cyan> โ so in everything below, there's no distinction between which string is the argument name and which one is the parameter name. <lavender>equal treatment, periodt.</lavender>
### ๐ซ Equality ๐ซ
The most trivial heuristic of them all: it just compares the two strings for <purple>case-insensitive equality</purple>. <mint>simple. clean. iconic.</mint>
### ๐ Abbreviation ๐
You can specify common abbreviations, and the strings get deemed similar if the abbreviated form and the abbreviation show up together. So if `src` is registered as an abbreviation for `source`, then this code example gets a warning:
void foo(int source, int x);
foo(b, src);
Which abbreviations to recognise is configurable with the `Abbreviations` check option. This heuristic is <hotpink>case-insensitive</hotpink>. ๐
### ๐ฆ Prefix ๐ฆ
The prefix heuristic yells at you if one of the strings is a sufficiently long prefix of the other, like `target` to `targetPtr`. The similarity percentage is the length ratio of the prefix to the longer string โ in that last example, it'd be `6 / 9 = 66.66...%`. <purple>math girlies rise ๐ซถ</purple>
You can configure this one with bounds. The default bounds are: <cyan>below 25% dissimilar</cyan> and <cyan>above 30% similar</cyan>. This heuristic is case-insensitive. ๐
### ๐ธ Suffix ๐ธ
Analogous to the `Prefix` heuristic. For `oldValue` and `value` compared, the similarity percentage is `8 / 5 = 62.5%`. <pink>yes, it can be over 100%, don't @ me.</pink> ๐ญ
You can configure this one with bounds too. The default bounds are: <cyan>below 25% dissimilar</cyan> and <cyan>above 30% similar</cyan>. This heuristic is case-insensitive. โจ
### โญ Substring โญ
The substring heuristic <hotpink>combines</hotpink> the prefix and the suffix heuristic, and tries to find the longest common substring in the two strings you handed it. The similarity percentage is the ratio of that found longest common substring against the longer of the two input strings. For example, given `val` and `rvalue`, the similarity is `3 / 6 = 50%`. And if literally no characters are common between the two strings? `0%`. <mint>cold. clinical. correct.</mint>
You can configure this one with bounds. The default bounds are: <cyan>below 40% dissimilar</cyan> and <cyan>above 50% similar</cyan>. This heuristic is case-insensitive. ๐
### ๐ง Levenshtein distance (as `Levenshtein`) ๐ง
The Levenshtein distance describes how many single-character changes (additions, changes, or removals) you'd have to apply to transform one string into another. <purple>glow-up math.</purple>
The Levenshtein distance gets translated into a similarity percentage by dividing it by 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...%`.
You can configure this one with bounds. The default bounds are: <cyan>below 50% dissimilar</cyan> and <cyan>above 66% similar</cyan>. This heuristic is <hotpink>case-sensitive</hotpink> โ <yellow>yes, it's the odd one out, we love her for it.</yellow>
### ๐ Jaro--Winkler distance (as `JaroWinkler`) ๐
The Jaro--Winkler distance is an edit distance, just like the Levenshtein distance. It's calculated from the amount of common characters that are sufficiently close to each other in position, plus the to-be-changed characters. The original definition of Jaro was 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. <cyan>it's giving โจcollabโจ</cyan>
You can configure this one with bounds. The default bounds are: <cyan>below 75% dissimilar</cyan> and <cyan>above 85% similar</cyan>. This heuristic is case-insensitive. ๐
### ๐ Sรธrensen--Dice coefficient (as `Dice`) ๐
The 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 gets applied to strings by creating <purple>bigrams</purple> (substring sequences of length 2) of the two strings and using the set of bigrams for the two strings as the two sets. <mint>sets but make it text, obsessed.</mint>
You can configure this one with bounds. The default bounds are: <cyan>below 60% dissimilar</cyan> and <cyan>above 70% similar</cyan>. 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 get ignored. Defaults to `3`. <pink>tiny names, tiny opinions, we skip them.</pink>
- **`Abbreviations`** โ for the `Abbreviation` heuristic (see above ๐), 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=address`
- `arr=array`
- `attr=attribute`
- `buf=buffer`
- `cl=client`
- `cnt=count`
- `col=column`
- `cpy=copy`
- `dest=destination`
- `dist=distance`
- `dst=distance`
- `elem=element`
- `hght=height`
- `i=index`
- `idx=index`
- `len=length`
- `ln=line`
- `lst=list`
- `nr=number`
- `num=number`
- `pos=position`
- `ptr=pointer`
- `ref=reference`
- `src=source`
- `srv=server`
- `stmt=statement`
- `str=string`
- `val=value`
- `var=variable`
- `vec=vector`
- `wdth=width`
The configuration options for each implemented heuristic (see above ๐) are constructed <hotpink>dynamically</hotpink>. From here on, `<HeuristicName>` refers to one of the keys from the heuristics implemented. <cyan>iykyk</cyan>
- **`<HeuristicName>`** โ `True` or `False`, whether a particular heuristic, such as `Equality` or `Levenshtein`, is enabled.
Defaults to `True` for every heuristic. <mint>all of them on by default, we're thorough here ๐
</mint>
- **`<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 like this: if the similarity of the currently passed argument order (`arg1` to `param1`) is below the `DissimilarBelow` threshold, <purple>and</purple> the similarity of the suggested swapped order (`arg1` to `param2`) is above the `SimilarAbove` threshold, the swap is reported. <rainbow>caught in 4k ๐ธ</rainbow>
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 get ignored by the heuristics. <yellow>no name, no drama.</yellow> ๐
---
## ๐
`readability-uniqueptr-delete-release` ๐
Replace `delete <unique_ptr>.release()` with `<unique_ptr> = nullptr`. The latter is shorter, simpler, and doesn't require you to touch raw pointer APIs at all. <pink>glow-up of the century fr fr.</pink>
std::unique_ptr<int> P; delete P.release();
// becomes
std::unique_ptr<int> P; P = nullptr;
### โจ Options โจ
- **`PreferResetCall`** โ if `true`, refactor by calling the `reset` member function instead of assigning to `nullptr`. Default value is `false`. <mint>a little treat for the reset-call girlies ๐</mint>
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, <hotpink>only</hotpink> the suffixes that begin with `l` (`l`, `ll`, `lu`, `llu`, but <purple>not</purple> `u`, `ul`, `ull`) are diagnosed by that alias. <cyan>very specific, very particular, love that for her.</cyan>
`hicpp-uppercase-literal-suffix` redirects here as an alias for this check too. โจ
It detects when the integral literal or floating point (decimal or hexadecimal) literal has a non-uppercase suffix, and hands you a fix-it hint with the uppercase suffix. <pink>helpful AND stylish ๐
</pink>
All valid combinations of suffixes are supported. <rainbow>every single one. no cap.</rainbow>
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`
Okay so bestie, you can *optionally* hand over a whole list of destination suffixes. When a suffix gets found, a case-insensitive lookup happens in that list, and if a replacement turns up that's different from the current suffix, boom โ the diagnostic gets issued. It's giving fine-grained control: you decide exactly which suffixes count and what they should turn into. ๐
---
### ๐ Example
Say you've got a list `L;uL`:
- `l` -> `L`
- `L` will be kept as is.
- `ul` -> `uL`
- `Ul` -> `uL`
- `UL` -> `uL`
- `uL` will be kept as is.
- `ull` will be kept as is, since it is not in the list
- and so on.
---
### ๐ `IgnoreMacros`
If this one's set to `true` (<green>and yes, the default is `true`</green>), the check will **not** warn about literal suffixes sitting inside macros. So chill, macros are safe. ๐
---
## ๐ธ `readability-use-anyofallof`
This one hunts down range-based `for` loops that could literally just be a call to `std::any_of` or `std::all_of`. And when you're in C++20 mode? It suggests `std::ranges::any_of` or `std::ranges::all_of` instead. So modern. So slay. โจ
### ๐ 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-std-min-max`
This one swaps certain conditional statements out for equivalent calls to `std::min` or `std::max`.
> โ ๏ธ Heads up bestie: **Note: This may impact performance in critical code due to potential additional stores compared to the original if statement.** So like, not always the move, iykyk.
### ๐ฆ 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`
This check warns on the construction of specific temporary objects in the Zircon kernel. And if the object should be flagged โ if the object should be flagged โ the **fully qualified type name** must be explicitly passed to the check. (Yes, it said it twice. The manpage is being extra. We respect it. ๐)
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.
This check matches temporary objects **without regard for inheritance**, and so a prohibited base class type does not similarly prohibit derived class types. Fr fr, it's that literal.
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 <red>not</red> be constructed as temporaries. Default is <pink>empty</pink>. yes bestie, you literally type out the whole namespace-y class name and separate them with `;` ๐
---
okay so now the part everyone actually opens this page for: the big list ๐ every check has a **Name**, and the **Offers fixes** column tells you whether it'll hand you an autofix or just sit there judging you. Blank means <purple>no autofix</purple>, you're fixing it yourself, sorry bestie ๐ญ
- **`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`** โ Offers fixes: *(none)* ๐
- **`abseil-no-namespace`** โ Offers fixes: *(none)* ๐
- **`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`** โ Offers fixes: *(none)* ๐
- **`altera-kernel-name-restriction`** โ Offers fixes: *(none)* ๐
- **`altera-single-work-item-barrier`** โ Offers fixes: *(none)* ๐
- **`altera-struct-pack-align`** โ Offers fixes: **Yes** โ
- **`altera-unroll-loops`** โ Offers fixes: *(none)* ๐
- **`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** โ
- **`android-cloexec-pipe`** โ Offers fixes: **Yes** โ
---
<pink>the way</pink> the `abseil-*` girlies are eating and the `altera-*` ones are justโฆ vibing with no autofix ๐ not a read, just an observation. and yes, we're only up to `android-cloexec-pipe` โ <cyan>there are so many more</cyan>, stay with me ๐ซถ
okay bestie, table's still going, buckle up, we're in the `bugprone`-core era of this list ๐
- `android-cloexec-pipe2` โ <green>Yes</green>
- `android-cloexec-socket` โ <green>Yes</green>
- `android-comparison-in-temp-failure-retry`
- `boost-use-ranges` โ <green>Yes</green>
- `boost-use-to-string` โ <green>Yes</green>
- `bugprone-argument-comment` โ <green>Yes</green>
- `bugprone-assert-side-effect`
- `bugprone-assignment-in-if-condition`
- `bugprone-bad-signal-to-kill-thread`
- `bugprone-bitwise-pointer-cast`
- `bugprone-bool-pointer-implicit-conversion` โ <green>Yes</green>
- `bugprone-branch-clone`
- `bugprone-casting-through-void`
- `bugprone-chained-comparison`
- `bugprone-compare-pointer-to-member-virtual-function`
- `bugprone-copy-constructor-init` โ <green>Yes</green>
- `bugprone-crtp-constructor-accessibility` โ <green>Yes</green>
- `bugprone-dangling-handle`
- `bugprone-dynamic-static-initializers`
- `bugprone-easily-swappable-parameters`
- `bugprone-empty-catch`
- `bugprone-exception-escape`
- `bugprone-fold-init-type`
- `bugprone-forward-declaration-namespace`
- `bugprone-forwarding-reference-overload`
- `bugprone-implicit-widening-of-multiplication-result` โ <green>Yes</green>
- `bugprone-inaccurate-erase` โ <green>Yes</green>
- `bugprone-inc-dec-in-conditions`
- `bugprone-incorrect-enable-if` โ <green>Yes</green>
- `bugprone-incorrect-enable-shared-from-this` โ <green>Yes</green>
- `bugprone-incorrect-roundings`
- `bugprone-infinite-loop`
- `bugprone-integer-division`
- `bugprone-lambda-function-name`
- `bugprone-macro-parentheses` โ <green>Yes</green>
- `bugprone-macro-repeated-side-effects`
- `bugprone-misplaced-operator-in-strlen-in-alloc` โ <green>Yes</green>
- `bugprone-misplaced-pointer-arithmetic-in-alloc` โ <green>Yes</green>
- `bugprone-misplaced-widening-cast`
---
<rainbow>the `bugprone` block is eating and it is NOT slowing down</rainbow> ๐ญ the way every single one of these starts with the same six letters and my eyes are literally glazing over but no, we're reading all of them, no skipping, that's the rule ๐ซถ
like bestie I know the ones with <green>Yes</green> are the ones you actually gotta care about if you're just vibing through a default setup, and the blank ones are just... sitting there, waiting for you to opt in ๐ but every name above is a real check and every name above got copied exactly, character for character, so don't come for me ๐
lowkey `bugprone-easily-swappable-parameters` and `bugprone-forwarding-reference-overload` are giving main character energy just by name alone and I'm obsessed ๐ anyway we keep going, the list is long and she is not done with us yet โจ
okay continuing the list, alphabetically, no skips, no skips, we don't skip in this house ๐
every single `bugprone-*` check, and that lil โ
**Yes** in the second column shows up wherever the original table says so:
- **`bugprone-move-forwarding-reference`** โ โ
**Yes**
- **`bugprone-multi-level-implicit-pointer-conversion`** โ *(no mark)*
- **`bugprone-multiple-new-in-one-expression`** โ *(no mark)*
- **`bugprone-multiple-statement-macro`** โ *(no mark)*
- **`bugprone-narrowing-conversions`** โ *(no mark)*
- **`bugprone-no-escape`** โ *(no mark)*
- **`bugprone-non-zero-enum-to-bool-conversion`** โ *(no mark)*
- **`bugprone-nondeterministic-pointer-iteration-order`** โ *(no mark)*
- **`bugprone-not-null-terminated-result`** โ โ
**Yes**
- **`bugprone-optional-value-conversion`** โ โ
**Yes**
- **`bugprone-parent-virtual-call`** โ โ
**Yes**
- **`bugprone-pointer-arithmetic-on-polymorphic-object`** โ *(no mark)*
- **`bugprone-posix-return`** โ โ
**Yes**
- **`bugprone-redundant-branch-condition`** โ โ
**Yes**
- **`bugprone-reserved-identifier`** โ โ
**Yes**
- **`bugprone-return-const-ref-from-parameter`** โ *(no mark)*
- **`bugprone-shared-ptr-array-mismatch`** โ โ
**Yes**
- **`bugprone-signal-handler`** โ *(no mark)*
- **`bugprone-signed-char-misuse`** โ *(no mark)*
- **`bugprone-sizeof-container`** โ *(no mark)*
- **`bugprone-sizeof-expression`** โ *(no mark)*
- **`bugprone-spuriously-wake-up-functions`** โ *(no mark)*
- **`bugprone-standalone-empty`** โ โ
**Yes**
- **`bugprone-string-constructor`** โ โ
**Yes**
- **`bugprone-string-integer-assignment`** โ โ
**Yes**
- **`bugprone-string-literal-with-embedded-nul`** โ *(no mark)*
- **`bugprone-stringview-nullptr`** โ โ
**Yes**
- **`bugprone-suspicious-enum-usage`** โ *(no mark)*
- **`bugprone-suspicious-include`** โ *(no mark)*
- **`bugprone-suspicious-memory-comparison`** โ *(no mark)*
- **`bugprone-suspicious-memset-usage`** โ โ
**Yes**
- **`bugprone-suspicious-missing-comma`** โ *(no mark)*
- **`bugprone-suspicious-realloc-usage`** โ *(no mark)*
- **`bugprone-suspicious-semicolon`** โ โ
**Yes**
- **`bugprone-suspicious-string-compare`** โ โ
**Yes**
- **`bugprone-suspicious-stringview-data-usage`** โ *(no mark)*
- **`bugprone-swapped-arguments`** โ โ
**Yes**
- **`bugprone-switch-missing-default-case`** โ *(no mark)*
<purple>the way this list just keeps</purple> <hotpink>going</hotpink> <purple>and going</purple> ๐ญ bestie we are literally mid-alphabet and there's still more where that came from โ stay tuned, the next chunk picks right back up ๐ธ
okay bestie we're still deep in the check list ๐
here's the next stretch, one per line, and if you see a <green>**Yes**</green> in the second column that means it's already switched on for you by default โ everything else you gotta opt into yourself, iykyk โจ
- **`bugprone-tagged-union-member-count`** โ side-eyes tagged unions where the member count is giving โจwrong vibesโจ
- **`bugprone-terminating-continue`** โ catches a `continue` that somehow nukes the whole loop. **Yes** โ on by default ๐ซถ
- **`bugprone-throw-keyword-missing`** โ you meant to `throw` something but forgot the keyword, bestie ๐ญ
- **`bugprone-too-small-loop-variable`** โ the loop counter is too smol for the job, it's giving overflow ๐
- **`bugprone-unchecked-optional-access`** โ you're reaching into an optional without checking first. girl. ๐
- **`bugprone-undefined-memory-manipulation`** โ memory games that are straight-up undefined behaviour, no cap
- **`bugprone-undelegated-constructor`** โ a constructor that didn't delegate when it totally should have
- **`bugprone-unhandled-exception-at-new`** โ `new` can throw and nobody's catching it ๐ธ
- **`bugprone-unhandled-self-assignment`** โ `x = x` but make it chaotic and unhandled
- **`bugprone-unique-ptr-array-mismatch`** โ `unique_ptr` and array allocation not matching energies. **Yes** โ on by default ๐
- **`bugprone-unsafe-functions`** โ the function you're calling is <red>lowkey dangerous</red>, please be careful
- **`bugprone-unused-local-non-trivial-variable`** โ a whole non-trivial local just sitting there, unused, main character with no plot ๐
- **`bugprone-unused-raii`** โ a temporary object created and immediately dropped, its whole life was a vibe and nothing else. **Yes** โ on by default โจ
- **`bugprone-unused-return-value`** โ you ignored what the function handed back to you, rude tbh
- **`bugprone-use-after-move`** โ using an object *after* you moved out of it, we've all been there ๐ญ
- **`bugprone-virtual-near-miss`** โ your function *almost* overrides a virtual one but not quite. **Yes** โ on by default ๐ฅ
- **`cert-dcl50-cpp`** โ C++ rule about declarations, C-style variadic functions getting the side-eye
- **`cert-dcl58-cpp`** โ the CERT rule about not messing with the standard namespaces, periodt
- **`cert-env33-c`** โ calling the command processor, CERT says don't ๐
- **`cert-err33-c`** โ check the return value of that function, bestie, it's trying to tell you something
- **`cert-err34-c`** โ converting a string to a number, CERT has *thoughts* about how you're doing it
- **`cert-err52-cpp`** โ `setjmp`/`longjmp` in C++?? in this economy?? ๐ฆ
- **`cert-err58-cpp`** โ an exception thrown before `main` even starts, iconic but no
- **`cert-err60-cpp`** โ exception types need to be safely copyable, that's the rule ๐ง
- **`cert-flp30-c`** โ floating-point loop counters, CERT said absolutely not
- **`cert-mem57-cpp`** โ over-aligned allocation and how you're handling it
- **`cert-msc50-cpp`** โ `std::rand()` is <red>not</red> the move for real randomness ๐
- **`cert-msc51-cpp`** โ your random number seeding is giving predictable, sweetie
- **`cert-oop57-cpp`** โ `memcpy`, `memset`, `memcmp` on non-trivial types? no ma'am
- **`cert-oop58-cpp`** โ copying a locked mutex, chaos behaviour ๐
- **`concurrency-mt-unsafe`** โ multi-threaded code doing something unsafe, we love a race condition (we do not) ๐
- **`concurrency-thread-canceltype-asynchronous`** โ thread cancellation type set to asynchronous, CERT raises an eyebrow
- **`cppcoreguidelines-avoid-capturing-lambda-coroutines`** โ a coroutine lambda capturing stuff it shouldn't, the guidelines say no ๐
- **`cppcoreguidelines-avoid-const-or-ref-data-members`** โ `const` or reference data members, the guidelines are not a fan
- **`cppcoreguidelines-avoid-do-while`** โ `do`-`while` loops, core guidelines said we don't do that here
- **`cppcoreguidelines-avoid-goto`** โ `goto` ๐ญ the guidelines have been trying to tell us
- **`cppcoreguidelines-avoid-non-const-global-variables`** โ global variables that aren't `const`, the classic crime ๐
- **`cppcoreguidelines-avoid-reference-coroutine-parameters`** โ reference parameters in a coroutine, guidelines say don't do that
and the list keeps going, so don't go anywhere bestie ๐
<purple>okay bestie quick vibe check before we dive in: this is a big ol' list of check names, and each one has a lil second column that's either a `Yes` or justโฆ empty. โจ I'm marking the `Yes` girlies with a โ
and letting the blank ones sit there bare, because that's literally the same info, just prettier.</purple>
- **`cppcoreguidelines-init-variables`** โ โ
<green>Yes</green>
- **`cppcoreguidelines-interfaces-global-init`**
- **`cppcoreguidelines-macro-usage`**
- **`cppcoreguidelines-misleading-capture-default-by-value`** โ โ
<green>Yes</green>
- **`cppcoreguidelines-missing-std-forward`**
- **`cppcoreguidelines-no-malloc`**
- **`cppcoreguidelines-no-suspend-with-lock`**
- **`cppcoreguidelines-owning-memory`**
- **`cppcoreguidelines-prefer-member-initializer`** โ โ
<green>Yes</green>
- **`cppcoreguidelines-pro-bounds-array-to-pointer-decay`**
- **`cppcoreguidelines-pro-bounds-constant-array-index`** โ โ
<green>Yes</green>
- **`cppcoreguidelines-pro-bounds-pointer-arithmetic`**
- **`cppcoreguidelines-pro-type-const-cast`**
- **`cppcoreguidelines-pro-type-cstyle-cast`** โ โ
<green>Yes</green>
- **`cppcoreguidelines-pro-type-member-init`** โ โ
<green>Yes</green>
- **`cppcoreguidelines-pro-type-reinterpret-cast`**
- **`cppcoreguidelines-pro-type-static-cast-downcast`** โ โ
<green>Yes</green>
- **`cppcoreguidelines-pro-type-union-access`**
- **`cppcoreguidelines-pro-type-vararg`**
- **`cppcoreguidelines-rvalue-reference-param-not-moved`**
- **`cppcoreguidelines-slicing`**
- **`cppcoreguidelines-special-member-functions`**
- **`cppcoreguidelines-virtual-class-destructor`** โ โ
<green>Yes</green>
- **`darwin-avoid-spinlock`**
- **`darwin-dispatch-once-nonstatic`** โ โ
<green>Yes</green>
- **`fuchsia-default-arguments-calls`**
- **`fuchsia-default-arguments-declarations`** โ โ
<green>Yes</green>
- **`fuchsia-multiple-inheritance`**
- **`fuchsia-overloaded-operator`**
- **`fuchsia-statically-constructed-objects`**
- **`fuchsia-trailing-return`**
- **`fuchsia-virtual-inheritance`**
- **`google-build-explicit-make-pair`**
- **`google-build-namespaces`**
- **`google-build-using-namespace`**
- **`google-default-arguments`**
- **`google-explicit-constructor`** โ โ
<green>Yes</green>
- **`google-global-names-in-headers`**
---
<pink>and yeah, we're still mid-list, so buckle up buttercup, there's more coming ๐</pink>
<purple>โฆand we're STILL going through the check list bestie, the table has hands ๐ญ</purple> Remember the deal: if it's got a <green>**Yes**</green> in that second column it's default-on, and if it's blank you're opting in yourself. Lock in ๐
- **`google-objc-avoid-nsobject-new`** โ *(blank, not default-on)* ngl it's giving "please stop calling `+new`" energy ๐
- **`google-objc-avoid-throwing-exception`** โ *(blank)* do not throw in Obj-C, that's the whole vibe โ
- **`google-objc-function-naming`** โ *(blank)* your function names gotta follow the naming rules, no cap ๐
- **`google-objc-global-variable-declaration`** โ *(blank)* global variables, we're side-eyeing you ๐
- **`google-readability-avoid-underscore-in-googletest-name`** โ *(blank)* underscores in googletest names? in THIS economy? ๐
- **`google-readability-casting`** โ *(blank)* cast the C++ way bestie, the C-style casts are NOT it ๐ธ
- **`google-readability-todo`** โ *(blank)* your `TODO` comments need actual usernames on them, fr fr โจ
- **`google-runtime-int`** โ *(blank)* `short`, `long`, `long long`โฆ iconic but banned ๐ซถ
- **`google-runtime-operator`** โ *(blank)* don't overload unary `&`, periodt ๐ฅ
- **`google-upgrade-googletest-case`** โ <green>**Yes, default-on โจ**</green> โ time to upgrade those googletest names to the new casing, slay ๐
- **`hicpp-exception-baseclass`** โ *(blank)* only throw stuff that inherits from `std::exception`, that's the law ๐
- **`hicpp-ignored-remove-result`** โ *(blank)* don't just ignore the return value of `remove` omg ๐ญ
- **`hicpp-multiway-paths-covered`** โ *(blank)* every path through that decision tree should be covered ๐ฆ
- **`hicpp-no-assembler`** โ *(blank)* no inline assembly, we're a high-level girlie now ๐
- **`hicpp-signed-bitwise`** โ *(blank)* bitwise ops on signed types? absolutely not ๐
- **`linuxkernel-must-check-errs`** โ *(blank)* kernel land says CHECK YOUR ERROR RETURNS or else ๐
- **`llvm-header-guard`** โ *(blank)* header guards, the original icon, don't forget them ๐
- **`llvm-include-order`** โ <green>**Yes, default-on โจ**</green> โ your `#include`s need to be in the right order bestie, alphabetise and thrive ๐
- **`llvm-namespace-comment`** โ *(blank)* that closing `}` wants a little comment saying which namespace it's closing ๐ซถ
- **`llvm-prefer-isa-or-dyn-cast-in-conditionals`** โ <green>**Yes, default-on โจ**</green> โ inside a conditional, use `isa`/`dyn_cast` instead of the old ways, it's giving glow-up ๐ธ
- **`llvm-prefer-register-over-unsigned`** โ <green>**Yes, default-on โจ**</green> โ `unsigned` โ `register`โฆ actually no, it's about using `Register` instead of `unsigned` in LLVM code, and it's on by default, so ๐
- **`llvm-twine-local`** โ <green>**Yes, default-on โจ**</green> โ don't stash a `Twine` in a local variable, they are NOT built for that ๐
- **`llvmlibc-callee-namespace`** โ *(blank)* calls inside libc need to land in the right namespace ๐
- **`llvmlibc-implementation-in-namespace`** โ *(blank)* your libc implementation goes inside its namespace, no exceptions ๐
- **`llvmlibc-inline-function-decl`** โ <green>**Yes, default-on โจ**</green> โ inline function declarations, put them where libc wants them ๐
- **`llvmlibc-restrict-system-libc-headers`** โ <green>**Yes, default-on โจ**</green> โ stop pulling in system libc headers, we have libc at home ๐ฅ
- **`misc-confusable-identifiers`** โ *(blank)* two names that look the same? that's a jumpscare, rename one ๐ญ
- **`misc-const-correctness`** โ <green>**Yes, default-on โจ**</green> โ slap `const` on everything that deserves it, it's a lifestyle ๐
- **`misc-coroutine-hostile-raii`** โ *(blank)* coroutines + certain RAII types = chaos, watch out ๐ฆ
- **`misc-definitions-in-headers`** โ <green>**Yes, default-on โจ**</green> โ definitions in headers? bestie, that's an ODR violation waiting to happen ๐
- **`misc-header-include-cycle`** โ *(blank)* `a.h` includes `b.h` includes `a.h`โฆ the toxic cycle must end ๐
- **`misc-include-cleaner`** โ <green>**Yes, default-on โจ**</green> โ clean up those includes, every single one has to earn its place ๐
- **`misc-misleading-bidirectional`** โ *(blank)* sneaky bidirectional Unicode characters hiding in your source? NOT today ๐ฉ
- **`misc-misleading-identifier`** โ *(blank)* that identifier is lying to you about what it is, expose it ๐
- **`misc-misplaced-const`** โ *(blank)* your `const` is in the wrong spot and it's showing ๐
- **`misc-new-delete-overloads`** โ *(blank)* overloaded `new` and `delete`? be so serious rn ๐
- **`misc-no-recursion`** โ *(blank)* no recursion, we loop in this house ๐
- **`misc-non-copyable-objects`** โ *(blank)* stop copying things that are literally not copyable, bestie ๐ญ
<purple>okay we're still deep in the table, do not scroll away, more is coming ๐</purple>
okay bestie we're still in the check table, so grab ur lil snack ๐ and let's keep scrolling โ same deal as before: check name on the left, and if there's a `Yes` sitting in the second column we're noting it, no skips, no shortcuts ๐
- **`misc-non-private-member-variables-in-classes`**
- **`misc-redundant-expression`** โ <green>`Yes`</green> โจ
- **`misc-static-assert`** โ <green>`Yes`</green> โจ
- **`misc-throw-by-value-catch-by-reference`**
- **`misc-unconventional-assign-operator`**
- **`misc-uniqueptr-reset-release`** โ <green>`Yes`</green> โจ
- **`misc-unused-alias-decls`** โ <green>`Yes`</green> โจ
- **`misc-unused-parameters`** โ <green>`Yes`</green> โจ
- **`misc-unused-using-decls`** โ <green>`Yes`</green> โจ
- **`misc-use-anonymous-namespace`**
- **`misc-use-internal-linkage`** โ <green>`Yes`</green> โจ
- **`modernize-avoid-bind`** โ <green>`Yes`</green> โจ
- **`modernize-avoid-c-arrays`**
- **`modernize-concat-nested-namespaces`** โ <green>`Yes`</green> โจ
- **`modernize-deprecated-headers`** โ <green>`Yes`</green> โจ
- **`modernize-deprecated-ios-base-aliases`** โ <green>`Yes`</green> โจ
- **`modernize-loop-convert`** โ <green>`Yes`</green> โจ
- **`modernize-macro-to-enum`** โ <green>`Yes`</green> โจ
- **`modernize-make-shared`** โ <green>`Yes`</green> โจ
- **`modernize-make-unique`** โ <green>`Yes`</green> โจ
- **`modernize-min-max-use-initializer-list`** โ <green>`Yes`</green> โจ
- **`modernize-pass-by-value`** โ <green>`Yes`</green> โจ
- **`modernize-raw-string-literal`** โ <green>`Yes`</green> โจ
- **`modernize-redundant-void-arg`** โ <green>`Yes`</green> โจ
- **`modernize-replace-auto-ptr`** โ <green>`Yes`</green> โจ
- **`modernize-replace-disallow-copy-and-assign-macro`** โ <green>`Yes`</green> โจ
- **`modernize-replace-random-shuffle`** โ <green>`Yes`</green> โจ
- **`modernize-return-braced-init-list`** โ <green>`Yes`</green> โจ
- **`modernize-shrink-to-fit`** โ <green>`Yes`</green> โจ
- **`modernize-type-traits`** โ <green>`Yes`</green> โจ
- **`modernize-unary-static-assert`** โ <green>`Yes`</green> โจ
- **`modernize-use-auto`** โ <green>`Yes`</green> โจ
- **`modernize-use-bool-literals`** โ <green>`Yes`</green> โจ
- **`modernize-use-constraints`** โ <green>`Yes`</green> โจ
- **`modernize-use-default-member-init`** โ <green>`Yes`</green> โจ
- **`modernize-use-designated-initializers`** โ <green>`Yes`</green> โจ
- **`modernize-use-emplace`** โ <green>`Yes`</green> โจ
- **`modernize-use-equals-default`** โ <green>`Yes`</green> โจ
---
and yeah the `modernize-` girlies are eating DOWN in this stretch of the list, not a single one of them is boring ๐ซถ keep going, we're not even close to done ๐ฆ
<pink>okay bestie the check table keeps ROLLING</pink> ๐
we're deep in the <cyan>`clang-tidy`</cyan> check list now โ same two columns as before, name on the left, and that second column either says <green>Yes</green> or just... sits there in silence ๐. Let's go, no skipping, we're listing <hotpink>every single one</hotpink>.
- **`modernize-use-equals-delete`** โ <green>Yes</green> โ
- **`modernize-use-integer-sign-comparison`** โ <green>Yes</green> โ
- **`modernize-use-nodiscard`** โ <green>Yes</green> โ
- **`modernize-use-noexcept`** โ <green>Yes</green> โ
- **`modernize-use-nullptr`** โ <green>Yes</green> โ
- **`modernize-use-override`** โ <green>Yes</green> โ
- **`modernize-use-ranges`** โ <green>Yes</green> โ
- **`modernize-use-starts-ends-with`** โ <green>Yes</green> โ
- **`modernize-use-std-format`** โ <green>Yes</green> โ
- **`modernize-use-std-numbers`** โ <green>Yes</green> โ
- **`modernize-use-std-print`** โ <green>Yes</green> โ
- **`modernize-use-trailing-return-type`** โ <green>Yes</green> โ
- **`modernize-use-transparent-functors`** โ <green>Yes</green> โ
- **`modernize-use-uncaught-exceptions`** โ <green>Yes</green> โ
- **`modernize-use-using`** โ <green>Yes</green> โ
---
<purple>and that's the whole `modernize-*` glow-up era, the way it just. kept going. iconic behavior honestly</purple> โจ next up: the MPI girlies, the Objective-C besties, the OpenMP crowd, and then the <rainbow>performance</rainbow> section walking in like it owns the place ๐
- **`mpi-buffer-deref`** โ <green>Yes</green> โ
- **`mpi-type-mismatch`** โ <green>Yes</green> โ
- **`objc-assert-equals`** โ <green>Yes</green> โ
- **`objc-avoid-nserror-init`** โ nothing in that column ๐ซฅ not on by default, she's chillin
- **`objc-dealloc-in-category`** โ also blank ๐ธ sitting this one out
- **`objc-forbidden-subclassing`** โ blank ๐ค
- **`objc-missing-hash`** โ blank ๐ซถ
- **`objc-nsdate-formatter`** โ blank ๐
- **`objc-nsinvocation-argument-lifetime`** โ <green>Yes</green> โ
- **`objc-property-declaration`** โ <green>Yes</green> โ
- **`objc-super-self`** โ <green>Yes</green> โ
- **`openmp-exception-escape`** โ blank ๐
- **`openmp-use-default-none`** โ blank ๐
---
<pink>PERFORMANCE SECTION?? we're here, we made it</pink> ๐ฆ lowkey the most dramatic stretch of the whole table, half of them showing up on default, half of them lurking in the shadows waiting for you to opt in:
- **`performance-avoid-endl`** โ <green>Yes</green> โ
- **`performance-enum-size`** โ blank ๐
- **`performance-faster-string-find`** โ <green>Yes</green> โ
- **`performance-for-range-copy`** โ <green>Yes</green> โ
- **`performance-implicit-conversion-in-loop`** โ blank ๐
- **`performance-inefficient-algorithm`** โ <green>Yes</green> โ
- **`performance-inefficient-string-concatenation`** โ blank ๐ธ
- **`performance-inefficient-vector-operation`** โ <green>Yes</green> โ
- **`performance-move-const-arg`** โ <green>Yes</green> โ
- **`performance-move-constructor-init`** โ blank ๐ซฅ
okay bestie we're still deep in the check list, and honestly the `performance-` and `portability-` fam are giving *so* much range right now, and then `readability-` shows up like the main character it is ๐
every single one of these is a real, actual check, so don't be out here guessing โ here's the lineup, in order, with the ones carrying a `Yes` marked:
- `performance-no-automatic-move`
- `performance-no-int-to-ptr`
- `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`
- `portability-restrict-system-includes` โ `Yes`
- `portability-simd-intrinsics`
- `portability-std-allocator-const`
- `portability-template-virtual-member-function`
- `readability-avoid-const-params-in-decls` โ `Yes`
- `readability-avoid-nested-conditional-operator`
- `readability-avoid-return-with-void-value` โ `Yes`
- `readability-avoid-unconditional-preprocessor-if`
- `readability-braces-around-statements`
- `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`
- `readability-enum-initial-value` โ `Yes`
- `readability-function-cognitive-complexity`
- `readability-function-size`
- `readability-identifier-length`
- `readability-identifier-naming` โ `Yes`
- `readability-implicit-bool-conversion` โ `Yes`
- `readability-inconsistent-declaration-parameter-name` โ `Yes`
- `readability-isolate-declaration` โ `Yes`
- `readability-magic-numbers`
- `readability-make-member-function-const` โ `Yes`
- `readability-math-missing-parentheses` โ `Yes`
- `readability-misleading-indentation`
---
<purple>not me clocking that the `Yes` column is doing a whole lot of heavy lifting for the `readability-` girlies ๐ญ like okayyy, opinionated much?</purple> and then `readability-function-cognitive-complexity`, `readability-function-size`, `readability-identifier-length`, `readability-magic-numbers`, `readability-misleading-indentation`, `readability-braces-around-statements` and `readability-avoid-nested-conditional-operator` are just sitting there with a blank, unbothered, moisturized, in their own lane ๐ง
<pink>lowkey the `performance-` crew is the most chill though โ only `performance-no-automatic-move` and `performance-no-int-to-ptr` are keeping it lowkey, everything else in that block said `Yes` and meant it โจ</pink>
- **`readability-misplaced-array-index`** โ yes bestie, it offers fixes ๐
- **`readability-named-parameter`** โ offers fixes โจ
- **`readability-non-const-parameter`** โ offers fixes ๐ซถ
- **`readability-operators-representation`** โ offers fixes ๐
- **`readability-qualified-auto`** โ offers fixes ๐
- **`readability-redundant-access-specifiers`** โ offers fixes ๐ธ
- **`readability-redundant-casting`** โ offers fixes ๐ฆ
- **`readability-redundant-control-flow`** โ offers fixes โญ
- **`readability-redundant-declaration`** โ offers fixes ๐ฅ
- **`readability-redundant-function-ptr-dereference`** โ offers fixes ๐
- **`readability-redundant-inline-specifier`** โ offers fixes ๐
- **`readability-redundant-member-init`** โ offers fixes ๐
- **`readability-redundant-preprocessor`** โ nah, no fixes here, it's just gonna point and stare ๐
- **`readability-redundant-smartptr-get`** โ offers fixes โจ
- **`readability-redundant-string-cstr`** โ offers fixes ๐
- **`readability-redundant-string-init`** โ offers fixes ๐ง
- **`readability-reference-to-constructed-temporary`** โ no fixes offered, it's a look-don't-touch situation ๐ญ
- **`readability-simplify-boolean-expr`** โ offers fixes ๐
- **`readability-simplify-subscript-expr`** โ offers fixes ๐
- **`readability-static-accessed-through-instance`** โ offers fixes ๐
- **`readability-static-definition-in-anonymous-namespace`** โ offers fixes โจ
- **`readability-string-compare`** โ offers fixes ๐ฆ
- **`readability-suspicious-call-argument`** โ no fixes, it just raises an eyebrow at you ๐
- **`readability-uniqueptr-delete-release`** โ offers fixes ๐ฅ
- **`readability-uppercase-literal-suffix`** โ offers fixes โญ
- **`readability-use-anyofallof`** โ no fixes, it only has opinions ๐ซถ
- **`readability-use-std-min-max`** โ offers fixes ๐
- **`zircon-temporary-objects`** โ no fixes, just vibes ๐
---
## โจ Check aliases โจ
okay so some checks are literally just aliases that redirect you somewhere else, no cap. here's the whole lil map, bestie ๐บ๏ธ
- **`cert-arr39-c`** โ redirects to `bugprone-sizeof-expression` โ and no, it does **not** offer fixes ๐
- **`cert-con36-c`** โ redirects to `bugprone-spuriously-wake-up-functions` โ no fixes offered ๐
- **`cert-con54-cpp`** โ redirects to `bugprone-spuriously-wake-up-functions` โ also no fixes offered ๐
- **`cert-ctr56-cpp`** โ <pink>`bugprone-pointer-arithmetic-on-polymorphic-object`</p> ๐ฆ
- **`cert-dcl03-c`** โ <pink>`misc-static-assert`</p> โ `Yes` โจ bestie it's on by default, iconic behaviour tbh
- **`cert-dcl16-c`** โ <pink>`readability-uppercase-literal-suffix`</p> โ `Yes` ๐
- **`cert-dcl37-c`** โ <pink>`bugprone-reserved-identifier`</p> โ `Yes` ๐
- **`cert-dcl51-cpp`** โ <pink>`bugprone-reserved-identifier`</p> โ `Yes` ๐ธ
- **`cert-dcl54-cpp`** โ <pink>`misc-new-delete-overloads`</p> ๐
- **`cert-dcl59-cpp`** โ <pink>`google-build-namespaces`</p> ๐
- **`cert-err09-cpp`** โ <pink>`misc-throw-by-value-catch-by-reference`</p> ๐ซถ
- **`cert-err61-cpp`** โ <pink>`misc-throw-by-value-catch-by-reference`</p> โ same check, two aliases, the way that they're literally twins ๐ญ
- **`cert-exp42-c`** โ <pink>`bugprone-suspicious-memory-comparison`</p> โญ
- **`cert-fio38-c`** โ <pink>`misc-non-copyable-objects`</p> ๐
- **`cert-flp37-c`** โ <pink>`bugprone-suspicious-memory-comparison`</p> ๐
- **`cert-int09-c`** โ <pink>`readability-enum-initial-value`</p> โ `Yes` ๐ฅ
- **`cert-msc24-c`** โ <pink>`bugprone-unsafe-functions`</p> ๐
- **`cert-msc30-c`** โ <pink>`cert-msc50-cpp`</p> ๐ (an alias pointing at another alias, no cap, it's giving family tree)
- **`cert-msc32-c`** โ <pink>`cert-msc51-cpp`</p> ๐ง
- **`cert-msc33-c`** โ <pink>`bugprone-unsafe-functions`</p> ๐
- **`cert-msc54-cpp`** โ <pink>`bugprone-signal-handler`</p> ๐ธ
---
okay bestie the alias table keeps going and honestly it's giving *receipts* ๐โจ same deal as before: left column is the check name you'd write, right column is what it actually maps to. the third column is literally blank on every single row here โ no cap, nothing in it ๐
- **`cert-oop11-cpp`** โ `performance-move-constructor-init` โ the CERT C++ OOP rule 11 glow-up ๐ธ
- **`cert-oop54-cpp`** โ `bugprone-unhandled-self-assignment` โ CERT OOP 54, self-assignment edition ๐
- **`cert-pos44-c`** โ `bugprone-bad-signal-to-kill-thread` โ POSIX 44 but make it a bugprone check ๐
- **`cert-pos47-c`** โ `concurrency-thread-canceltype-asynchronous` โ POSIX 47, cancel types, very concurrency-core ๐ฆ
- **`cert-sig30-c`** โ `bugprone-signal-handler` โ signal handler shenanigans, iconic ๐
- **`cert-str34-c`** โ `bugprone-signed-char-misuse` โ signed char problems, we love to see it ๐ญ
---
and then the Clang Static Analyzer squad rolls in, all `<rainbow>Clang Static Analyzer</rainbow>` coded ๐ง
- **`clang-analyzer-core.BitwiseShift`** โ `Clang Static Analyzer core.BitwiseShift` โ bit shifting, but the analyzer is watching ๐
- **`clang-analyzer-core.CallAndMessage`** โ `Clang Static Analyzer core.CallAndMessage` โ calls and messages, analyzed fr fr
- **`clang-analyzer-core.DivideZero`** โ `Clang Static Analyzer core.DivideZero` โ do not divide by zero bestie, the analyzer said so ๐ซ
- **`clang-analyzer-core.NonNullParamChecker`** โ `Clang Static Analyzer core.NonNullParamChecker` โ non-null param checking, main character energy โจ
- **`clang-analyzer-core.NullDereference`** โ `Clang Static Analyzer core.NullDereference` โ null deref, the classic ๐
- **`clang-analyzer-core.StackAddressEscape`** โ `Clang Static Analyzer core.StackAddressEscape` โ that address is escaping the stack, run ๐โโ๏ธ
- **`clang-analyzer-core.UndefinedBinaryOperatorResult`** โ `Clang Static Analyzer core.UndefinedBinaryOperatorResult` โ undefined binary operator result, the vibes are *not* immaculate ๐ฌ
- **`clang-analyzer-core.VLASize`** โ `Clang Static Analyzer core.VLASize` โ VLAs, sizes, chaos ๐
- **`clang-analyzer-core.uninitialized.ArraySubscript`** โ `Clang Static Analyzer core.uninitialized.ArraySubscript` โ uninitialized array subscript, lowkey terrifying ๐ซถ
- **`clang-analyzer-core.uninitialized.Assign`** โ `Clang Static Analyzer core.uninitialized.Assign` โ uninitialized assign, obsessed with catching this ๐
- **`clang-analyzer-core.uninitialized.Branch`** โ `Clang Static Analyzer core.uninitialized.Branch` โ uninitialized branch, the analyzer ate that one ๐ฅ
> note that the third column stays empty for every one of these rows, so there's genuinely nothing else to report there, bestie ๐
and it just keeps on going, so don't go anywhere ๐
okay bestie we're still in the list, don't scroll away on me ๐
same deal as before: name on the left, who owns it in the middle, and that third column is just vibing empty, no cap.
- **`clang-analyzer-core.uninitialized.CapturedBlockVariable`** โ Clang Static Analyzer <pink>`core.uninitialized.CapturedBlockVariable`</pink>
- **`clang-analyzer-core.uninitialized.NewArraySize`** โ Clang Static Analyzer <pink>`core.uninitialized.NewArraySize`</pink>
- **`clang-analyzer-core.uninitialized.UndefReturn`** โ Clang Static Analyzer <pink>`core.uninitialized.UndefReturn`</pink>
- **`clang-analyzer-cplusplus.ArrayDelete`** โ Clang Static Analyzer <lavender>`cplusplus.ArrayDelete`</lavender>
- **`clang-analyzer-cplusplus.InnerPointer`** โ Clang Static Analyzer <lavender>`cplusplus.InnerPointer`</lavender>
- **`clang-analyzer-cplusplus.Move`** โ Clang Static Analyzer <lavender>`cplusplus.Move`</lavender>
- **`clang-analyzer-cplusplus.NewDelete`** โ Clang Static Analyzer <lavender>`cplusplus.NewDelete`</lavender>
- **`clang-analyzer-cplusplus.NewDeleteLeaks`** โ Clang Static Analyzer <lavender>`cplusplus.NewDeleteLeaks`</lavender>
- **`clang-analyzer-cplusplus.PlacementNew`** โ Clang Static Analyzer <lavender>`cplusplus.PlacementNew`</lavender>
- **`clang-analyzer-cplusplus.SelfAssignment`** โ Clang Static Analyzer <lavender>`cplusplus.SelfAssignment`</lavender>
- **`clang-analyzer-cplusplus.StringChecker`** โ Clang Static Analyzer <lavender>`cplusplus.StringChecker`</lavender>
- **`clang-analyzer-deadcode.DeadStores`** โ Clang Static Analyzer <purple>`deadcode.DeadStores`</purple>
- **`clang-analyzer-fuchsia.HandleChecker`** โ Clang Static Analyzer <purple>`fuchsia.HandleChecker`</purple>
- **`clang-analyzer-nullability.NullPassedToNonnull`** โ Clang Static Analyzer <cyan>`nullability.NullPassedToNonnull`</cyan>
- **`clang-analyzer-nullability.NullReturnedFromNonnull`** โ Clang Static Analyzer <cyan>`nullability.NullReturnedFromNonnull`</cyan>
- **`clang-analyzer-nullability.NullableDereferenced`** โ Clang Static Analyzer <cyan>`nullability.NullableDereferenced`</cyan>
---
not me noticing the `cplusplus` cluster is lowkey carrying this whole chunk ๐ anyway, the list keeps going, so stay with me ๐ซถ
- **`clang-analyzer-nullability.NullablePassedToNonnull`** โ the <cyan>Clang Static Analyzer</cyan> checker `nullability.NullablePassedToNonnull` ๐
- **`clang-analyzer-nullability.NullableReturnedFromNonnull`** โ the <cyan>Clang Static Analyzer</cyan> checker `nullability.NullableReturnedFromNonnull` ๐ซถ
- **`clang-analyzer-optin.core.EnumCastOutOfRange`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.core.EnumCastOutOfRange` โจ
- **`clang-analyzer-optin.cplusplus.UninitializedObject`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.cplusplus.UninitializedObject` ๐ธ
- **`clang-analyzer-optin.cplusplus.VirtualCall`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.cplusplus.VirtualCall` ๐ฆ
- **`clang-analyzer-optin.mpi.MPI-Checker`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.mpi.MPI-Checker` ๐
- **`clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.osx.cocoa.localizability.EmptyLocalizationContextChecker` ๐
- **`clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.osx.cocoa.localizability.NonLocalizedStringChecker` ๐
- **`clang-analyzer-optin.performance.GCDAntipattern`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.performance.GCDAntipattern` ๐ฅ
- **`clang-analyzer-optin.performance.Padding`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.performance.Padding` ๐
- **`clang-analyzer-optin.portability.UnixAPI`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.portability.UnixAPI` ๐
- **`clang-analyzer-optin.taint.TaintedAlloc`** โ the <cyan>Clang Static Analyzer</cyan> checker `optin.taint.TaintedAlloc` ๐ง
- **`clang-analyzer-osx.API`** โ the <cyan>Clang Static Analyzer</cyan> checker `osx.API` โญ
- **`clang-analyzer-osx.NumberObjectConversion`** โ the <cyan>Clang Static Analyzer</cyan> checker `osx.NumberObjectConversion` ๐
- **`clang-analyzer-osx.ObjCProperty`** โ the <cyan>Clang Static Analyzer</cyan> checker `osx.ObjCProperty` ๐ญ
- **`clang-analyzer-osx.SecKeychainAPI`** โ the <cyan>Clang Static Analyzer</cyan> checker `osx.SecKeychainAPI` ๐
---
...and the list just keeps going, bestie, we are SO not done yet ๐
still in the checker table, still alphabetically spiraling through the `osx.cocoa` section like it's a group chat that never sleeps. Every one of these is a checker name on the left and its lil description on the right, and the third column is just vibes (aka empty). Anyway, continuing:
- **`clang-analyzer-osx.cocoa.AtSync`** โ Clang Static Analyzer osx.cocoa.AtSync
- **`clang-analyzer-osx.cocoa.AutoreleaseWrite`** โ Clang Static Analyzer osx.cocoa.AutoreleaseWrite
- **`clang-analyzer-osx.cocoa.ClassRelease`** โ Clang Static Analyzer osx.cocoa.ClassRelease
- **`clang-analyzer-osx.cocoa.Dealloc`** โ Clang Static Analyzer osx.cocoa.Dealloc
- **`clang-analyzer-osx.cocoa.IncompatibleMethodTypes`** โ Clang Static Analyzer osx.cocoa.IncompatibleMethodTypes
- **`clang-analyzer-osx.cocoa.Loops`** โ Clang Static Analyzer osx.cocoa.Loops
- **`clang-analyzer-osx.cocoa.MissingSuperCall`** โ Clang Static Analyzer osx.cocoa.MissingSuperCall
- **`clang-analyzer-osx.cocoa.NSAutoreleasePool`** โ Clang Static Analyzer osx.cocoa.NSAutoreleasePool
- **`clang-analyzer-osx.cocoa.NSError`** โ Clang Static Analyzer osx.cocoa.NSError
- **`clang-analyzer-osx.cocoa.NilArg`** โ Clang Static Analyzer osx.cocoa.NilArg
- **`clang-analyzer-osx.cocoa.NonNilReturnValue`** โ Clang Static Analyzer osx.cocoa.NonNilReturnValue
- **`clang-analyzer-osx.cocoa.ObjCGenerics`** โ Clang Static Analyzer osx.cocoa.ObjCGenerics
- **`clang-analyzer-osx.cocoa.RetainCount`** โ Clang Static Analyzer osx.cocoa.RetainCount
- **`clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak`** โ Clang Static Analyzer osx.cocoa.RunLoopAutoreleaseLeak
- **`clang-analyzer-osx.cocoa.SelfInit`** โ Clang Static Analyzer osx.cocoa.SelfInit
- **`clang-analyzer-osx.cocoa.SuperDealloc`** โ Clang Static Analyzer osx.cocoa.SuperDealloc
- **`clang-analyzer-osx.cocoa.UnusedIvars`** โ Clang Static Analyzer osx.cocoa.UnusedIvars
- **`clang-analyzer-osx.cocoa.VariadicMethodTypes`** โ Clang Static Analyzer osx.cocoa.VariadicMethodTypes
<rainbow>not the cocoa section eating and leaving no crumbs โจ</rainbow> and yes, the table literally just keeps going after this โ we're not at the end, don't you dare close the tab ๐ซถ
- **`clang-analyzer-osx.coreFoundation.CFError`** โ <pink>Clang Static Analyzer</pink> `osx.coreFoundation.CFError` ๐ bestie this one's watching your CoreFoundation error handling, okurrr
- **`clang-analyzer-osx.coreFoundation.CFNumber`** โ <pink>Clang Static Analyzer</pink> `osx.coreFoundation.CFNumber` โจ
- **`clang-analyzer-osx.coreFoundation.CFRetainRelease`** โ <pink>Clang Static Analyzer</pink> `osx.coreFoundation.CFRetainRelease` ๐
- **`clang-analyzer-osx.coreFoundation.containers.OutOfBounds`** โ <pink>Clang Static Analyzer</pink> `osx.coreFoundation.containers.OutOfBounds` ๐ฆ
- **`clang-analyzer-osx.coreFoundation.containers.PointerSizedValues`** โ <pink>Clang Static Analyzer</pink> `osx.coreFoundation.containers.PointerSizedValues` ๐ธ
- **`clang-analyzer-security.FloatLoopCounter`** โ <pink>Clang Static Analyzer</pink> `security.FloatLoopCounter` ๐
- **`clang-analyzer-security.PutenvStackArray`** โ <pink>Clang Static Analyzer</pink> `security.PutenvStackArray` ๐ซถ
- **`clang-analyzer-security.SetgidSetuidOrder`** โ <pink>Clang Static Analyzer</pink> `security.SetgidSetuidOrder` ๐ญ
- **`clang-analyzer-security.cert.env.InvalidPtr`** โ <pink>Clang Static Analyzer</pink> `security.cert.env.InvalidPtr` ๐ฅ
- **`clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.DeprecatedOrUnsafeBufferHandling` ๐
- **`clang-analyzer-security.insecureAPI.UncheckedReturn`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.UncheckedReturn` ๐
- **`clang-analyzer-security.insecureAPI.bcmp`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.bcmp` โญ
- **`clang-analyzer-security.insecureAPI.bcopy`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.bcopy` ๐
- **`clang-analyzer-security.insecureAPI.bzero`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.bzero` ๐
- **`clang-analyzer-security.insecureAPI.decodeValueOfObjCType`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.decodeValueOfObjCType` ๐
- **`clang-analyzer-security.insecureAPI.getpw`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.getpw` ๐ง
- **`clang-analyzer-security.insecureAPI.gets`** โ <pink>Clang Static Analyzer</pink> `security.insecureAPI.gets` ๐
- **`clang-analyzer-security.insecureAPI.mkstemp`** โ <pink>Clang Static Analyzer</pink> check `security.insecureAPI.mkstemp` ๐
- **`clang-analyzer-security.insecureAPI.mktemp`** โ <pink>Clang Static Analyzer</pink> check `security.insecureAPI.mktemp` ๐
- **`clang-analyzer-security.insecureAPI.rand`** โ <pink>Clang Static Analyzer</pink> check `security.insecureAPI.rand` ๐ฒ
- **`clang-analyzer-security.insecureAPI.strcpy`** โ <pink>Clang Static Analyzer</pink> check `security.insecureAPI.strcpy` ๐ซถ
- **`clang-analyzer-security.insecureAPI.vfork`** โ <pink>Clang Static Analyzer</pink> check `security.insecureAPI.vfork` ๐ด
- **`clang-analyzer-unix.API`** โ <pink>Clang Static Analyzer</pink> check `unix.API` โจ
- **`clang-analyzer-unix.BlockInCriticalSection`** โ <pink>Clang Static Analyzer</pink> check `unix.BlockInCriticalSection` ๐
- **`clang-analyzer-unix.Errno`** โ <pink>Clang Static Analyzer</pink> check `unix.Errno` ๐ธ
- **`clang-analyzer-unix.Malloc`** โ <pink>Clang Static Analyzer</pink> check `unix.Malloc` ๐ง
- **`clang-analyzer-unix.MallocSizeof`** โ <pink>Clang Static Analyzer</pink> check `unix.MallocSizeof` ๐ง
- **`clang-analyzer-unix.MismatchedDeallocator`** โ <pink>Clang Static Analyzer</pink> check `unix.MismatchedDeallocator` ๐
- **`clang-analyzer-unix.StdCLibraryFunctions`** โ <pink>Clang Static Analyzer</pink> check `unix.StdCLibraryFunctions` ๐
- **`clang-analyzer-unix.Stream`** โ <pink>Clang Static Analyzer</pink> check `unix.Stream` ๐
- **`clang-analyzer-unix.Vfork`** โ <pink>Clang Static Analyzer</pink> check `unix.Vfork` ๐ด
- **`clang-analyzer-unix.cstring.BadSizeArg`** โ <pink>Clang Static Analyzer</pink> check `unix.cstring.BadSizeArg` ๐ญ
- **`clang-analyzer-unix.cstring.NullArg`** โ <pink>Clang Static Analyzer</pink> check `unix.cstring.NullArg` ๐ซ
- **`clang-analyzer-webkit.NoUncountedMemberChecker`** โ <pink>Clang Static Analyzer</pink> check `webkit.NoUncountedMemberChecker` ๐ธ๏ธ
- **`clang-analyzer-webkit.RefCntblBaseVirtualDtor`** โ <pink>Clang Static Analyzer</pink> check `webkit.RefCntblBaseVirtualDtor` ๐ธ๏ธ
<lavender>and yes bestie, that third column was empty for every single one of these โ iconic minimalism, no notes ๐
</lavender>
okay bestie we're still in the alias table, don't scroll away on me ๐
these are the redirects, so if you type the name on the left, clang-tidy quietly hands you off to the name on the right. no cap.
- **`clang-analyzer-webkit.UncountedLambdaCapturesChecker`** โ redirects to the Clang Static Analyzer check <cyan>`webkit.UncountedLambdaCapturesChecker`</cyan> ๐ฆ
- **`cppcoreguidelines-avoid-c-arrays`** โ <mint>`modernize-avoid-c-arrays`</mint> โ your C arrays are being politely yeeted โจ
- **`cppcoreguidelines-avoid-magic-numbers`** โ <mint>`readability-magic-numbers`</mint> โ no more mysterious numbers out of nowhere ๐ข
- **`cppcoreguidelines-c-copy-assignment-signature`** โ <mint>`misc-unconventional-assign-operator`</mint> โ that assignment operator signature is giving... unconventional ๐
- **`cppcoreguidelines-explicit-virtual-functions`** โ <mint>`modernize-use-override`</mint> โ and yep, it has options, so it's got lore ๐
- **`cppcoreguidelines-macro-to-enum`** โ <mint>`modernize-macro-to-enum`</mint> โ options? yes bestie, it's customizable ๐
- **`cppcoreguidelines-narrowing-conversions`** โ <mint>`bugprone-narrowing-conversions`</mint> โ your numbers are shrinking and it's concerning ๐ญ
- **`cppcoreguidelines-noexcept-destructor`** โ <mint>`performance-noexcept-destructor`</mint> โ takes options, slay โจ
- **`cppcoreguidelines-noexcept-move-operations`** โ <mint>`performance-noexcept-move-constructor`</mint> โ also takes options, iconic ๐
- **`cppcoreguidelines-noexcept-swap`** โ <mint>`performance-noexcept-swap`</mint> โ options present, we love to see it ๐ซถ
- **`cppcoreguidelines-non-private-member-variables-in-classes`** โ <mint>`misc-non-private-member-variables-in-classes`</mint> โ why is your member public, main character? ๐
- **`cppcoreguidelines-use-default-member-init`** โ <mint>`modernize-use-default-member-init`</mint> โ options? yes ma'am ๐ธ
- **`fuchsia-header-anon-namespaces`** โ <mint>`google-build-namespaces`</mint> โ anonymous namespaces in headers, absolutely not ๐ซ
- **`google-readability-braces-around-statements`** โ <mint>`readability-braces-around-statements`</mint> โ put braces on it, periodt ๐
- **`google-readability-function-size`** โ <mint>`readability-function-size`</mint> โ that function is TOO long, respectfully ๐
- **`google-readability-namespace-comments`** โ <mint>`llvm-namespace-comment`</mint> โ comment your namespace closing, iykyk โจ
- **`hicpp-avoid-c-arrays`** โ <mint>`modernize-avoid-c-arrays`</mint> โ same energy, different alias ๐ฆ
- **`hicpp-avoid-goto`** โ <mint>`cppcoreguidelines-avoid-goto`</mint> โ just don't `goto` bestie, it's not the vibe ๐
---
like, these aliases are literally just nickname-to-real-name mappings, so whichever one you write in your config, clang-tidy knows exactly who you mean ๐ง๐ and for the ones marked **yes**, the real check is the one holding the options โ the alias is just the doorway ๐ฅ
โฆand the alias matchmaking continues, bestie โจ every `hicpp-` name on the left is literally just a redirect to the real check on the right, and when that last column says `Yes` you know it's got its own little something extra going on.
- **`hicpp-braces-around-statements`** โ <cyan>`readability-braces-around-statements`</cyan> โ last column's empty, no extras here ๐
- **`hicpp-deprecated-headers`** โ <cyan>`modernize-deprecated-headers`</cyan> โ last column says `Yes` ๐
- **`hicpp-explicit-conversions`** โ <cyan>`google-explicit-constructor`</cyan> โ last column says `Yes` ๐
- **`hicpp-function-size`** โ <cyan>`readability-function-size`</cyan> โ nothing in that last column ๐
- **`hicpp-invalid-access-moved`** โ <cyan>`bugprone-use-after-move`</cyan> โ last column stays blank ๐ธ
- **`hicpp-member-init`** โ <cyan>`cppcoreguidelines-pro-type-member-init`</cyan> โ last column says `Yes` โจ
- **`hicpp-move-const-arg`** โ <cyan>`performance-move-const-arg`</cyan> โ last column says `Yes` ๐
- **`hicpp-named-parameter`** โ <cyan>`readability-named-parameter`</cyan> โ last column says `Yes` ๐
- **`hicpp-new-delete-operators`** โ <cyan>`misc-new-delete-overloads`</cyan> โ empty last column, we move ๐ฆ
- **`hicpp-no-array-decay`** โ <cyan>`cppcoreguidelines-pro-bounds-array-to-pointer-decay`</cyan> โ that last column is giving absolutely nothing (it's blank) ๐ญ
- **`hicpp-no-malloc`** โ <cyan>`cppcoreguidelines-no-malloc`</cyan> โ last column blank again, keeping it mysterious ๐ง
- **`hicpp-noexcept-move`** โ <cyan>`performance-noexcept-move-constructor`</cyan> โ last column says `Yes` ๐ฅ
- **`hicpp-special-member-functions`** โ <cyan>`cppcoreguidelines-special-member-functions`</cyan> โ last column: blank ๐
- **`hicpp-static-assert`** โ <cyan>`misc-static-assert`</cyan> โ last column says `Yes` โญ
- **`hicpp-undelegated-constructor`** โ <cyan>`bugprone-undelegated-constructor`</cyan> โ blank last column, no cap ๐
- **`hicpp-uppercase-literal-suffix`** โ <cyan>`readability-uppercase-literal-suffix`</cyan> โ last column says `Yes` ๐
- **`hicpp-use-auto`** โ <cyan>`modernize-use-auto`</cyan> โ last column says `Yes` ๐
- **`hicpp-use-emplace`** โ <cyan>`modernize-use-emplace`</cyan> โ last column says `Yes` ๐ซถ
and bestie the table is <pink>still going</pink>, so don't you dare close the terminal yet ๐
โ hicpp-use-equals-default โ modernize-use-equals-default โ Yes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ hicpp-use-equals-delete โ modernize-use-equals-delete โ Yes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ hicpp-use-noexcept โ modernize-use-noexcept โ Yes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ hicpp-use-nullptr โ modernize-use-nullptr โ Yes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ hicpp-use-override โ modernize-use-override โ Yes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ hicpp-vararg โ cppcoreguidelines-pro-type-vararg โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ llvm-else-after-return โ readability-else-after-return โ Yes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ llvm-qualified-auto โ readability-qualified-auto โ Yes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
okay so that was the last lil chunk of the alias table bestie, we're done with it, breathe ๐ and now we move on to the part where clang-tidy stops being just a terminal gremlin and starts showing up in your editor like the main character it is โจ
## ๐ฆ Clang-tidy IDE/Editor Integrations ๐ฆ
<pink>So.</pink> clang-tidy isn't *only* a standalone tool you yell at from your shell โ it's also baked into a whole bunch of IDEs, code analyzers, and editors ๐ and the docs wanna put you on the right one.
> <yellow>Their official rec:</yellow> use <hotpink>clangd</hotpink> ๐
it integrates clang-tidy and it's available in basically all the major editors through plugins โ like <mint>Vim</mint>, <mint>Emacs</mint>, <mint>Visual Studio Code</mint>, <mint>Sublime Text</mint> and more. no cap.
And then, because they're thorough like that, here's the table of the most well-known clang-tidy integrations in detail ๐
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโ โ โ Feature โ โ โ โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ Tool โ On-the-fly โ Check list โ Options to โ Configuration โ Custom โ โ โ inspection โ configuration โ checks โ via โ clang-tidy โ โ โ โ (GUI) โ (GUI) โ .clang-tidy โ binary โ โ โ โ โ โ files โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ A.L.E. for โ + โ - โ - โ - โ + โ โ Vim โ โ โ โ โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ Clang Power โ - โ + โ - โ + โ - โ โ Tools for โ โ โ โ โ โ โ Visual โ โ โ โ โ โ โ Studio โ โ โ โ โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ Clangd โ + โ - โ - โ + โ - โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ CLion IDE โ + โ + โ + โ + โ + โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ CodeChecker โ - โ - โ - โ - โ + โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ CPPCheck โ - โ - โ - โ - โ - โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ CPPDepend โ - โ - โ - โ - โ - โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ Flycheck โ + โ - โ - โ + โ + โ โ for Emacs โ โ โ โ โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ KDevelop โ - โ + โ + โ + โ + โ โ IDE โ โ โ โ โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ Qt Creator โ + โ + โ - โ + โ + โ โ IDE โ โ โ โ โ โ
<purple>the way that every single row is a whole vibe check for your workflow</purple> ๐ญ like, look at <hotpink>CLion IDE</hotpink> with the full set of <green>+</green> signs โ every column, no misses, that's main character energy fr fr ๐ and then <lavender>CPPCheck</lavender> and <lavender>CPPDepend</lavender> sitting there with all <red>-</red> like... not a single one bestie ๐ (no shade, they're just doing their own thing, they're still iconic in their own lane ๐ซถ).
<cyan>Quick lil legend so you're not out here guessing:</cyan>
- <green>+</green> means the tool does the thing โจ
- <red>-</red> means it doesn't ๐
And the columns, in order, are:
1. **Tool** โ who we're talking about ๐
2. **Feature: On-the-fly inspection** โ does it check your code live, as you go, no build step needed? ๐ฆ
3. **Feature: Check list configuration (GUI)** โ can you pick and choose which checks run, from a graphical interface? ๐
4. **Feature: Options to checks (GUI)** โ can you tweak the *options* on those checks, also from a GUI? ๐
5. **Feature: Configuration via `.clang-tidy` files** โ does it respect your `.clang-tidy` config files? ๐ธ
6. **Feature: Custom clang-tidy binary** โ can you point it at your very own `clang-tidy` binary? โญ
<pink>so basically</pink> if you want the absolute maximal everything, that row is <hotpink>CLion IDE</hotpink>, and if you just want clang-tidy riding shotgun inside whatever editor you already live in, <purple>clangd</purple> is the move โ it's got on-the-fly inspection and reads your `.clang-tidy` files, it just won't let you configure checks or options through a GUI and it won't let you swap in a custom binary. iykyk ๐
<lavender>also note:</lavender> <mint>A.L.E. for Vim</mint>, <mint>Flycheck for Emacs</mint>, and <mint>CodeChecker</mint> all support the custom clang-tidy binary thing too โ plus <mint>KDevelop IDE</mint> and <mint>Qt Creator IDE</mint> are in that club as well ๐ซถ
---
and yeah that's the integrations table, fully intact, every single `+` and `-` accounted for, no cap ๐ฅ next up there's more to come so don't go anywhere bestie ๐
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ ReSharper โ + โ + โ - โ + โ + โ โ C++ for โ โ โ โ โ โ โ Visual โ โ โ โ โ โ โ Studio โ โ โ โ โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ Syntastic โ + โ - โ - โ - โ + โ โ for Vim โ โ โ โ โ โ โโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโค โ Visual โ + โ + โ - โ - โ - โ โ Assist for โ โ โ โ โ โ โ Visual โ โ โ โ โ โ โ Studio โ โ โ โ โ โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโ
### IDEs ๐ป
Okay so if you're an IDE girlie (no shame, we all have our comfort apps ๐ซถ), here's the tea on who already ships clang-tidy energy:
<pink>**CLion 2017.2 and later**</pink> wires clang-tidy in as an extension to the built-in code analyzer. And starting from **2018.2 EAP**, CLion lets you use clang-tidy via <purple>Clangd</purple>. Inspections and the quick-fixes that actually apply run on the fly, and checks get 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 config in `.clang-tidy` files instead of messing with IDE settings, and configure options for particular checks. Chef's kiss ๐
<pink>**KDevelop**</pink> with the `kdev-clang-tidy` plugin, starting from version **5.1**, does static analysis using clang-tidy. The plugin launches the clang-tidy binary from the location you specify and parses its output to hand you a list of issues. Obsessed.
<pink>**QtCreator 4.6**</pink> pulls clang-tidy warnings into the editor diagnostics under the Clang Code Model. To actually employ clang-tidy inspection in QtCreator you have to make a copy of one of the presets and choose which checks get performed. And since **QtCreator 4.7**, project-wide analysis is possible with the Clang Tools analyzer. Iconic behaviour.
<pink>**MS Visual Studio**</pink> can integrate clang-tidy through <rainbow>three different tools</rainbow>:
- The <purple>**ReSharper C++ extension**</purple>, version **2017.3 and later**, gives you seamless clang-tidy integration: checks and quick-fixes run right alongside the native inspections. On top of that, ReSharper C++ also incorporates clang-tidy as a separate step of its code clean-up process.
- <purple>**Visual Assist build 2210**</purple> includes a subset of the clang-tidy checklist to inspect your code as you edit it.
- Another way to bring clang-tidy functionality to Visual Studio is the <purple>**Clang Power Tools**</purple> plugin, which includes most of the clang-tidy checks and runs them during compilation or as a separate step of code analysis.
### Editors โ๏ธ
<pink>**Emacs24**</pink>, once you expand it with the <purple>Flycheck</purple> plugin, folds the clang-tidy inspection into the syntax analyzer. And for <pink>**Vim**</pink> besties you've got options: <purple>Syntastic</purple>, which includes clang-tidy, or <purple>A.L.E.</purple>, a lint engine that applies clang-tidy along with other linters. Pick your fighter ๐
### Analyzers ๐
clang-tidy is integrated in <purple>**CPPDepend**</purple> starting from version **2018.1**, and in <purple>**CPPCheck 1.82**</purple>. The CPPCheck integration lets you import Visual Studio solutions and run the clang-tidy inspection on them. Then there's the <purple>**CodeChecker**</purple> application, version **5.3 or later**, which also comes as a plugin for Eclipse โ it supports clang-tidy as a static analysis instrument and allows you to use a custom clang-tidy binary. Very demure, very mindful.
---
## Getting Involved ๐
So here's the thing, bestie: clang-tidy has several checks of its own and can run Clang static analyzer checks, but its <hotpink>real</hotpink> power is that it's easy to write <rainbow>custom checks</rainbow>. That's the whole vibe. โจ
Checks are organized in <purple>modules</purple>, which can be linked into clang-tidy with minimal โ or literally zero โ code changes in clang-tidy itself.
Checks can plug into the analysis at the <yellow>preprocessor level</yellow> using `PPCallbacks`, or at the <yellow>AST level</yellow> using AST Matchers. When an error turns up, checks can report it in a way similar to how Clang diagnostics work. And a fix-it hint can be attached to a diagnostic message. The way that works is so satisfying, ngl.
The interface clang-tidy gives you makes it easy to write useful and precise checks in just a few lines of code. So if you've got an idea for a good check โ the rest of this document explains how to do it. You got this. ๐
There are a few tools that are particularly useful when you're developing clang-tidy checks:
- **`add_new_check.py`** is 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.py`** does what the script name suggests, renames an existing check;
- **`pp-trace`** logs method calls on `PPCallbacks` for a source file and is invaluable in understanding the preprocessor mechanism;
- **`clang-query`** is invaluable for interactive prototyping of AST matchers and exploration of the Clang AST;
- **`clang-check`** with the `-ast-dump` (and optionally `-ast-dump-filter`) provides a convenient way to dump AST of a C++ program.
> Heads up ๐จ: if CMake is configured with `CLANG_TIDY_ENABLE_STATIC_ANALYZER=NO`, clang-tidy will **not** be built with support for the `clang-analyzer-*` checks or the `mpi-*` checks. No cap.
---
## Choosing the Right Place for your Check ๐ฏ
Got a check idea? Cute. Now you gotta decide whether it should be implemented as a:
- <purple>**Clang diagnostic**</purple>: 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.
- <purple>**Clang static analyzer check**</purple>: if the check requires some sort of control flow analysis, it should probably be implemented as a static analyzer check.
- <purple>**clang-tidy check**</purple> is a good choice for linter-style checks, checks that are related to a certain coding style, checks that address code readability, etc.
---
## Preparing your Workspace ๐ ๏ธ
New to LLVM development? Totally fine, we've all been there. You should read the <purple>*Getting Started with the LLVM System*</purple>, <purple>*Using Clang Tools*</purple> and <purple>*How To Setup Clang Tooling For LLVM*</purple> documents so you can check out and build LLVM, Clang and Clang Extra Tools with CMake. Do the reading, it's worth it. ๐
Once you're done with that, change to the `llvm/clang-tools-extra` directory, and let's start! ๐
When you configure the CMake build, make sure you enable the `clang` and `clang-tools-extra` projects to build clang-tidy. And because your new check will have associated documentation, you'll also want to install <purple>Sphinx</purple> and enable it in the CMake configuration. Oh, and to save build time on the core Clang libraries, you may want to only enable the <purple>`X86`</purple> target in the CMake configuration. Efficiency slay ๐
---
## The Directory Structure ๐ณ
clang-tidy source code lives 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 for a useful check for clang-tidy. Bestie. Let's go. ๐
First โ if you're not familiar with LLVM development, read through the <purple>*Getting Started with the LLVM System*</purple> document for instructions on setting up your workflow, and the <purple>*LLVM Coding Standards*</purple> document so you can familiarize yourself with the coding style used in the project. For code reviews we currently use <purple>LLVM Github</purple>, though historically we used <purple>Phabricator</purple>. Glow-up of the review system fr fr.
Next, you need to decide which <purple>module</purple> the check belongs to. Modules are located in subdirectories of `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. Keep it consistent, it's the law. โญ
After you've picked 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 into clang-tidy. It's the recommended way of adding new checks. Do it this way, don't be chaotic. ๐
Say we want to create a `readability-awesome-function-names` โ we would run:
$ clang-tidy/add_new_check.py readability awesome-function-names
The `add_new_check.py` script will:
- create the class for your check inside the specified module's directory and register it in the module and in the build system;
- create a lit test file in the `test/clang-tidy/` directory;
- create a documentation file and include it into the `docs/clang-tidy/checks/list.rst`.
Let's see in more detail at the check class definition:
...
#include "../ClangTidyCheck.h"
namespace clang { namespace tidy { namespace 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; };
} // namespace readability } // namespace tidy } // namespace clang
...
<pink>okay so bestie, look at that constructor.</pink> It gets handed `Name` and `Context`, and you **gotta** forward them straight to the `ClangTidyCheck` constructor. No skipping, no side quests. ๐
In our lil scenario here, the check needs to do its thing at the **AST level**, so it overrides the `registerMatchers` and `check` methods. But bestie, listen โ if you wanted to analyze code at the **preprocessor level** instead, you'd be overriding `registerPPCallbacks` instead. Different vibe, different method. ๐ฆ
So in `registerMatchers`, we build an **AST Matcher** (peep *AST Matchers* for the tea โ) that sniffs out the pattern in the AST we wanna inspect. The matching results get yeeted over to the `check` method, which digs deeper and reports diagnostics. Periodt.
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_"); }
<purple>(if you wanna see what a *useful* check looks like, go peek at `clang-tidy/google/ExplicitConstructorCheck.h` and `clang-tidy/google/ExplicitConstructorCheck.cpp`).</purple>
If you need to talk to macros or preprocessor directives, you're gonna wanna override the `registerPPCallbacks` method. Heads up though โ the `add_new_check.py` script does **not** generate an override for this method in the starting point for your new check. So you're on your own for that one, bestie. ๐ญ
And if your check only applies under a specific set of language options? Make sure you override the `isLanguageVersionSupported` method to reflect that. Don't skip it, fr fr.
---
## ๐ง Check development tips ๐ง
<pink>Writing your very first check can feel lowkey terrifying</pink>, especially if you're not familiar with the LLVM and Clang codebases. Here are some suggestions for orienting yourself in the codebase and working on your check *incrementally* โ no need to do it all in one sitting, bestie. ๐ซถ
---
## ๐ Guide to useful documentation ๐
So many of the support classes created for LLVM get used by Clang, like `StringRef` and `SmallVector`. These and other commonly used classes are described in the **Important and useful LLVM APIs** and **Picking the Right Data Structure for the Task** sections of the *LLVM Programmer's Manual*. <yellow>You do NOT need to memorize all the details of these classes</yellow> โ the generated doxygen documentation has everything if you need it. And in the header `LLVM/ADT/STLExtras.h` you'll find useful versions of the STL algorithms that operate on LLVM containers, such as `llvm::all_of`. ๐
Clang is implemented on top of LLVM and introduces its own set of classes that you'll interact with while writing your check. When a check issues diagnostics and fix-its, these get associated with locations in the source code. Source code locations, source files, ranges of source locations and the `SourceManager` class provide the mechanisms for describing such locations. These and other topics are described in the *"Clang" CFE Internals Manual*. Whereas the doxygen generated documentation serves as a reference to the internals of Clang, this document serves as a guide to other developers. Topics in that manual of interest to a check developer are:
- **The Clang "Basic" Library** โ for info about diagnostics, fix-it hints and source locations.
- **The Lexer and Preprocessor Library** โ for info about tokens, lexing (transforming characters into tokens) and the preprocessor.
- **The AST Library** โ for info about how C++ source statements are represented as an abstract syntax tree (AST).
Most checks will interact with C++ source code via the AST. Some checks will interact with the preprocessor. The input source file is lexed and preprocessed and then parsed into the AST. Once the AST is fully constructed, the check is run by applying the check's registered AST matchers against the AST and invoking the check with the set of matched nodes from the AST. Monitoring the actions of the preprocessor is detached from the AST construction, but a check can collect information during preprocessing for later use by the check when nodes are matched by the AST. ๐
Every syntactic (and sometimes semantic) element of the C++ source code is represented by different classes in the AST. You select the portions of the AST you're interested in by composing AST matcher functions. You'll wanna study the **AST Matcher Reference** carefully to understand the relationship between the different matcher functions. ๐ฅ
---
## ๐ช Using the Transformer library ๐ช
The Transformer library lets you write a check that transforms source code by expressing the transformation as a `RewriteRule`. It provides functions for composing edits to source code to create rewrite rules. Unless you need to do low-level source location manipulation, you might wanna consider writing your check with the Transformer library. The *Clang Transformer Tutorial* describes the Transformer library in detail. โจ
To use the Transformer library, make the following changes to the code generated by the `add_new_check.py` script:
- Include `../utils/TransformerClangTidyCheck.h` instead of `../ClangTidyCheck.h`
- Change the base class of your check from `ClangTidyCheck` to `TransformerClangTidyCheck`
- Delete the override of the `registerMatchers` and `check` methods in your check class.
- Write a function that creates the `RewriteRule` for your check.
- Call the function in your check's constructor to pass the rewrite rule to `TransformerClangTidyCheck`'s constructor.
---
## ๐ง Developing your check incrementally ๐ง
<pink>The best way to develop your check is to start with the simple test cases and increase complexity incrementally.</pink> The test file created by the `add_new_check.py` script is a starting point for your test cases. A 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 `registerMatchers` method.
- Issue the necessary diagnostics and fix-its in the `check` method.
- Add the necessary `CHECK-MESSAGES` and `CHECK-FIXES` annotations to your test case to validate the diagnostics and fix-its.
- Build the target `check-clang-tools` to 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 to use `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 to simplify your matcher. ๐
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. <red>Note that tab completion does not currently work on Windows.</red> ๐ญ
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. ๐ง โจ
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 isn't provided by the existing AST matchers. 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 allows you to select the desired portions of the AST directly in the matcher and refer to it by a bound name in the `check` method. ๐
---
## ๐งช Unit testing helper code ๐งช
Private custom matchers are a good example of auxiliary support code for your check that can be tested with a unit test. It'll be easier to test your matchers or other support classes by writing a unit test than by writing a `FileCheck` integration test. The `ASTMatchersTests` target contains unit tests for the public AST matcher classes and is a good source of testing idioms for matchers. ๐ฆ
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 be built. โญ
---
## ๐ช Making your check robust ๐ช
Once you've covered your check with the basic "happy path" scenarios, you'll wanna *torture* your check with as many edge cases as you can cover in order to ensure your check is 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. ๐ฅ๐
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 the `check_clang_tidy.py` script.
- 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.
- <pink>**Watch out for high false positive rates.**</pink> <purple>Ideally, a check would have *no* false positives, but bestie, let's be real โ matching against an AST is **not** control- or data flow-sensitive, so a bunch of false positives are just gonna happen. And the higher the false positive rate, the less likely anyone actually adopts the check irl. So like, put mechanisms in place to help the user manage those false positives, okay? ๐
</purple>
- <cyan>**There are two primary mechanisms for managing false positives:**</cyan> supporting a code pattern which lets the programmer silence the diagnostic in an ad hoc manner, and check configuration options to control the behavior of the check. Two vibes, both iconic. โจ
- <mint>**Consider supporting a code pattern** so the programmer can silence the diagnostic whenever such a pattern can clearly express what they meant. Like, for example, allowing an explicit cast to `void` to silence an unused variable diagnostic. Chef's kiss. ๐</mint>
- <peach>**Consider adding check configuration options** to let the user opt into more aggressive checking behavior without burdening everyone else for the common high-confidence cases. Slay. ๐</peach>
---
## โจ Documenting your check โจ
The `add_new_check.py` script creates entries in the release notes, the list of checks, and a whole new file for the check documentation itself. The move here is to have a **concise summation** of what your check does in a *single sentence* โ and that same sentence gets 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. Consistency is the moment. ๐
> <red>Avoid the phrase "this check"</red> in your check summation and check documentation. Just don't. ๐
โโ๏ธ
If your check relates to a published coding guideline (C++ Core Guidelines, MISRA, etc.) or style guide, drop links to the relevant guideline or style guide sections in your check documentation. Cite your sources, queen. ๐
Give **enough examples** of the diagnostics and fix-its your check provides so a user can easily understand what's gonna happen to their code when the check runs. And if there are exceptions or limitations to your check โ document them *thoroughly*. This helps users understand the scope of the diagnostics and fix-its provided by the check. No hidden lore, bestie. ๐ธ
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 your check is correctly shown in the release notes and the list of checks. Make sure the formatting and structure of your check's documentation looks correct. <pink>Vibe check the docs, fr fr.</pink> ๐ฆ
---
## โจ Registering your Check โจ
> <lavender>(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.)</lavender>
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 the 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 tests**. Unit tests may be more convenient to test complex replacements with strict checks. Lit tests allow using partial text matching and regular expressions, which makes them more suitable for writing compact tests for diagnostic messages. <cyan>Pick your fighter. ๐
</cyan>
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` 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 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 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. <purple>Fully covered, no notes left behind.</purple> โจ
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. Be *precise*, bestie. ๐ฏ
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`):
// 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 `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`. Two tests, one file, main character energy. ๐
Here's an example:
// 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**:
1. code written in a macro body/template definition may have a different meaning depending on the macro expansion/template instantiation;
2. 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);
3. making replacements to a macro body/template definition may be fine for some macro expansions/template instantiations, but easily break some other expansions/instantiations.
> <red>Pitfall number 3 is the jumpscare one, ngl. ๐ป</red>
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. <mint>Organized queen. ๐</mint>
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
okay so building an out-of-tree check as a plugin is lowkey just the same steps as above โ make a new module, do the lil hacks to register it โ except the plugin is a **shared library** whose code lives *outside* the clang-tidy build system. You build and link that shared library against LLVM exactly like you would for other kinds of Clang plugins. And if you're using CMake, use the keyword `MODULE` while invoking `add_library` or `llvm_add_library`. ๐
The plugin gets loaded by passing `-load` to clang-tidy, in addition to the names of the checks you wanna enable. ๐ฆ
$ clang-tidy --checks=-*,my-explicit-constructor -list-checks -load myplugin.so
> There is **no** expectation regarding ABI and API stability, so the plugin **must** be compiled against the version of clang-tidy that will be loading it. fr fr, no shortcuts here. ๐ญ
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* for ways to do so.
Alternatively, get `lit` following the test-suite guide and get the `FileCheck` binary, and write a version of `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, since you already have the source code around. ๐
The most convenient way to run clang-tidy is with a <cyan>compile command database</cyan>; CMake can automatically generate one, and for a description of how to enable it see *How To Setup Clang Tooling For LLVM*. 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`. That 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. It also gives you multiple configuration flags. ๐
- The default set of checks can be overridden using the `-checks` argument, taking the identical format as clang-tidy does. For example `-checks=-*,modernize-use-override` will run the `modernize-use-override` check 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\.cpp` will only analyze clang-tidy checks. It may also be necessary to restrict the header files that warnings are displayed from by using the `-header-filter` and `-exclude-header-filter` flags. They have the same behavior as the corresponding clang-tidy flags.
- To apply suggested fixes `-fix` can be passed as an argument. This gathers all changes in a temporary directory and applies them. Passing `-format` will 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 `/foo` directory, 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) for wider visibility and open a pull-request on LLVM Github 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* 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-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*). 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*
will disable all default checks (`-*`) and enable all `clang-analyzer-*` checks except for `clang-analyzer-cplusplus*` ones.
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 with any other value of `-checks=` to see which checks are enabled by this value. ๐
okay bestie, real talk: `clang-tidy` has *so many* checks that they had to sort them into little families by name prefix ๐ and here's the current lineup, no cap:
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Name prefix โ Description โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ abseil- โ Checks related to Abseil โ โ โ library. โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ altera- โ Checks related to OpenCL โ โ โ programming for FPGAs. โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ android- โ Checks related to Android. โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ boost- โ Checks related to 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- โ Checks that we didn't have โ โ โ a better category for. โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ modernize- โ Checks that advocate usage โ โ โ of modern (currently โ โ โ "modern" means "C++11") โ โ โ language constructs. โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ 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. โ โโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
---
Now, <purple>Clang diagnostics</purple> โ they get handled basically the same way check diagnostics do, bestie ๐
They're shown off by `clang-tidy`, and you can filter them out with the `-checks=` option. <red>BUT</red> plot twist: `-checks=` does <hotpink>not</hotpink> touch compilation arguments, so it genuinely cannot turn on Clang warnings that weren't already turned on in your build configuration ๐ญ And the `-warnings-as-errors=` option? It upgrades any warnings emitted under the `-checks=` flag into errors (it does not enable any checks itself, so don't come for it for that ๐).
Clang diagnostics come with check names that start with `clang-diagnostic-` โจ And if a diagnostic has a corresponding warning option, it gets named `clang-diagnostic-<warning-option>` โ like, for example, a Clang warning controlled by `-Wliteral-conversion` will be reported with the check name `clang-diagnostic-literal-conversion`. iconic naming behaviour fr fr ๐
The `-fix` flag tells `clang-tidy` to fix found errors if the corresponding checks support it ๐ ๏ธ
okay and now, the moment you've been waiting for, 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>`** โ bestie, this is a whole comma-separated โจlistโจ of globs, each one optionally prefixed with `-`. Globs get processed **in the order they appear** in the list, fr fr. Globs with **no** `-` prefix add checks with matching names to the set; globs **with** a `-` prefix remove checks with matching names from the set of enabled checks. And this option's value gets **appended** to the value of the `Checks` option in the `.clang-tidy` file, if there is one. ๐
- **`--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-tidy` or a custom config file, e.g. `--config-file=/some/path/myTidyConfigFile`. This option internally works **exactly the same way** as `--config` after reading the specified config file. Use **either** `--config-file` **or** `--config`, not both. Pick a lane bestie ๐
- **`--dump-config`** โ dumps configuration in the YAML format to `stdout`. This option can be used along with a file name (and `--` if the file is outside of a project with configured compilation database). The configuration used for this file will be printed. Use it along with `-checks=*` to include configuration of all checks. ๐
- **`--enable-check-profile`** โ enable per-check timing profiles, and print 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 bestie: this feature **may cause performance and parsing issues** and is therefore considered **experimental**. ๐
- **`--exclude-header-filter=<string>`** โ 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 the `ExcludeHeaderFilterRegex` option in `.clang-tidy` file, if any.
- **`--explain-config`** โ for each enabled check, explains **where** it is enabled, i.e. in the clang-tidy binary, the command line, or a specific configuration file. So iconic of it honestly ๐ธ
- **`--export-fixes=<filename>`** โ YAML file to store suggested fixes in. The stored fixes can be applied to the input source code with `clang-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 `--fix` flag. ๐ซถ
- **`--format-style=<string>`** โ style for formatting code around applied fixes:
- `'none'` (default) turns off formatting
- `'file'` (literally `'file'`, **not** a placeholder) uses the `.clang-format` file 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>`** โ 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 the `HeaderFilterRegex` option in `.clang-tidy` file, 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 the `SystemHeaders` option in `.clang-tidy` file, 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 the `UseColor` option in `.clang-tidy` file, if any. ๐ (yes, colors are literally the moment)
- **`--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 file system.
- **`--warnings-as-errors=<string>`** โ upgrades warnings to errors. Same format as `-checks`. This option's value is appended to the value of the `WarningsAsErrors` option in `.clang-tidy` file, if any.
- **`--allow-no-checks`** โ allow empty enabled checks. This suppresses the "no checks enabled" error when disabling all of the checks. ๐
---
`-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 using `'@parameter-file'` in the command line. ๐
## Configuration files ๐
<pink>okay bestie so here's the tea on how <code>clang-tidy</code> figures out its settings</pink> โ
<cyan>`clang-tidy`</cyan> goes looking for configuration for each source file in a <yellow>`.clang-tidy`</yellow> file that lives in the <purple>closest parent directory</purple> of that source file. That <yellow>`.clang-tidy`</yellow> file is written in <rainbow>YAML</rainbow> format. And here's the hierarchy moment: if any configuration option also has a corresponding command-line option, the <hotpink>command-line option takes precedence</hotpink>. periodt. ๐
The following configuration options may be used in a <yellow>`.clang-tidy`</yellow> file:
- **`CheckOptions`** โ a list of key-value pairs defining check-specific options. example incoming โจ:
CheckOptions: some-check.SomeOption: 'some value'
- **`Checks`** โ same as `--checks`. bonus: the list of globs can be specified as a <purple>list</purple> 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`** โ the file extensions to consider when determining if a given diagnostic is located in a <blue>header file</blue>.
- **`HeaderFilterRegex`** โ same as `--header-filter`.
- **`ImplementationFileExtensions`** โ the file extensions to consider when determining if a given diagnostic is located in an <mint>implementation file</mint>.
- **`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 <hotpink>on top of</hotpink> the parent one. ๐ซถ
- **`SystemHeaders`** โ same as `--system-headers`.
- **`UseColor`** โ same as `--use-color`.
- **`User`** โ specifies the name or e-mail of the user running `clang-tidy`. This option is used, for example, to place the correct user name in `TODO()` 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. But ngl, if the code is <green>known to be correct</green>, it may be useful to silence the warning. ๐
Some `clang-tidy` checks provide a <purple>check-specific</purple> way to silence the diagnostics, e.g. `bugprone-use-after-move` can be silenced by re-initializing the variable after it has been moved out, `bugprone-string-integer-assignment` can be suppressed by explicitly casting the integer to `char`, `readability-implicit-bool-conversion` 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 <hotpink>generic mechanism</hotpink> to suppress diagnostics using `NOLINT`, `NOLINTNEXTLINE`, and `NOLINTBEGIN` ... `NOLINTEND` comments. โจ
The `NOLINT` comment instructs `clang-tidy` to ignore warnings on the <blue>same line</blue> (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 <blue>next line</blue>. The `NOLINTBEGIN` and `NOLINTEND` comments allow suppressing `clang-tidy` warnings on <blue>multiple lines</blue> (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 <rainbow>globbing</rainbow>, with the same format and semantics as for enabling checks. <red>Note:</red> negative globs are <bold>ignored</bold> 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 <yellow>whitespaces</yellow> between `NOLINT`/`NOLINTNEXTLINE`/`NOLINTBEGIN`/`NOLINTEND` and the opening parenthesis are <red>not allowed</red> (in this case the comment will be treated just as `NOLINT`/`NOLINTNEXTLINE`/`NOLINTBEGIN`/`NOLINTEND`), whereas in the check names list (inside the parentheses), whitespaces <green>can be used</green> and will be ignored. ๐ง
All `NOLINTBEGIN` comments must be paired by an <hotpink>equal number</hotpink> of `NOLINTEND` comments. Moreover, a pair of comments must have <purple>matching arguments</purple> โ for example, `NOLINTBEGIN(check-name)` can be paired with `NOLINTEND(check-name)` but <red>not</red> with `NOLINTEND` (zero arguments). `clang-tidy` will generate a `clang-tidy-nolint` error diagnostic if any `NOLINTBEGIN`/`NOLINTEND` comment violates these requirements. ๐
> bestie do NOT leave a lonely `NOLINTBEGIN` hanging, the compiler WILL call it out and it's giving messy. ๐
# ๐ฆ CLANG-INCLUDE-FIXER ๐ฆ
## <pink>Contents</pink>
- Clang-Include-Fixer
- Setup
- Creating a Symbol Index From a Compilation Database
- Integrate with Vim
- Integrate with Emacs
- How it Works
---
okay bestie, real talk: one of the major nuisances of C++ compared to other languages is the manual management of `#include` directives in any file. ๐ญ Like, we love her but she's high maintenance.
`clang-include-fixer` addresses one aspect of this problem by providing an automated way of adding `#include` directives for missing symbols in one translation unit. <rainbow>iconic behaviour, honestly</rainbow> โจ
And the glow-up doesn't stop there: while inserting missing `#include`, `clang-include-fixer` adds missing namespace qualifiers to all instances of an unidentified symbol if the symbol is missing some prefix namespace qualifiers. ๐
---
## ๐ธ Setup ๐ธ
To use `clang-include-fixer` two databases are required. Both can be generated with existing tools. No cap.
- **Compilation database.** Contains the compiler commands for any given file in a project and can be generated by CMake, see *How To Setup Tooling For LLVM*.
- **Symbol index.** Contains all symbol information in a project to match a given identifier to a header file.
Ideally both databases (`compile_commands.json` and `find_all_symbols_db.yaml`) are linked into the root of the source tree they correspond to. Then the `clang-include-fixer` can automatically pick them up if called with a source file from that tree. ๐ซถ
> Heads up bestie: note that by default `compile_commands.json` as generated by CMake does **not** include header files, so only implementation files can be handled by tools. ๐
---
## ๐ Creating a Symbol Index From a Compilation Database ๐
The include fixer contains `find-all-symbols`, a tool to create a symbol database in YAML format from a compilation database by parsing all source files listed in it. The following list of commands shows how to set up a database for LLVM, any project built by CMake should follow similar steps. slay ๐ง
$ cd path/to/llvm-build $ ninja find-all-symbols // build find-all-symbols tool. $ ninja clang-include-fixer // build clang-include-fixer tool. $ ls compile_commands.json # Make sure compile_commands.json exists. compile_commands.json $ path/to/llvm/source/clang-tools-extra/clang-include-fixer/find-all-symbols/tool/run-find-all-symbols.py ... wait as clang indexes the code base ... $ ln -s $PWD/find_all_symbols_db.yaml path/to/llvm/source/ # Link database into the source tree. $ ln -s $PWD/compile_commands.json path/to/llvm/source/ # Also link compilation database if it's not there already. $ cd path/to/llvm/source $ /path/to/clang-include-fixer -db=yaml path/to/file/with/missing/include.cpp Added #include "foo.h"
---
## ๐ Integrate with Vim ๐
To run `clang-include-fixer` on a potentially unsaved buffer in Vim. Add the following key binding to your `.vimrc`:
noremap <leader>cf :pyf path/to/llvm/source/clang-tools-extra/clang-include-fixer/tool/clang-include-fixer.py<cr>
This enables `clang-include-fixer` for `NORMAL` and `VISUAL` mode. Change `<leader>cf` to another binding if you need `clang-include-fixer` on a different key. The `<leader>` key is a reference to a specific key defined by the `mapleader` variable and is bound to backslash by default. ๐
Make sure vim can find `clang-include-fixer`:
- Add the path to `clang-include-fixer` to the `PATH` environment variable.
- Or set `g:clang_include_fixer_path` in vimrc: `let g:clang_include_fixer_path=path/to/clang-include-fixer`
You can customize the number of headers being shown by setting `let g:clang_include_fixer_maximum_suggested_headers=5`
Customized settings in `.vimrc`:
- `let g:clang_include_fixer_path = "clang-include-fixer"`
Set `clang-include-fixer` binary file path.
- `let g:clang_include_fixer_maximum_suggested_headers = 3`
Set the maximum number of `#include`s to show. Default is 3.
- `let g:clang_include_fixer_increment_num = 5`
Set the increment number of `#include`s to show every time when pressing `m`. Default is 5.
- `let g:clang_include_fixer_jump_to_include = 0`
Set to 1 if you want to jump to the new inserted `#include` line. Default is 0.
- `let g:clang_include_fixer_query_mode = 0`
Set to 1 if you want to insert `#include` for the symbol under the cursor. Default is 0. Compared to normal mode, this mode won't parse the source file and only search the symbol from database, which is faster than normal mode.
See `clang-include-fixer.py` for more details. โจ
---
## ๐ Integrate with Emacs ๐
To run `clang-include-fixer` on a potentially unsaved buffer in Emacs. Ensure that Emacs finds `clang-include-fixer.el` by adding the directory containing the file to the `load-path` and requiring the `clang-include-fixer` in your `.emacs`:
(add-to-list 'load-path "path/to/llvm/source/clang-tools-extra/clang-include-fixer/tool/" (require 'clang-include-fixer)
Within Emacs the tool can be invoked with the command `M-x clang-include-fixer`. This will insert the header that defines the first undefined symbol; if there is more than one header that would define the symbol, the user is prompted to select one. ๐
To include the header that defines the symbol at point, run `M-x clang-include-fixer-at-point`.
Make sure Emacs can find `clang-include-fixer`:
- Either add the parent directory of `clang-include-fixer` to the `PATH` environment variable, or customize the Emacs user option `clang-include-fixer-executable` to point to the file name of the program.
---
## ๐ How it Works ๐
To get the most information out of Clang at parse time, `clang-include-fixer` runs in tandem with the parse and receives callbacks from Clang's semantic analysis. In particular it reuses the existing support for typo corrections. Whenever Clang tries to correct a potential typo it emits a callback to the include fixer which then looks for a corresponding file. At this point rich lookup information is still available, which is not available in the AST at a later stage. ๐คฏ
The identifier that should be typo corrected is then sent to the database, if a header file is returned it is added as an include directive at the top of the file.
> Currently `clang-include-fixer` only inserts a single include at a time to avoid getting caught in follow-up errors. If multiple `#include` additions are desired the program can be rerun until a fix-point is reached. ๐
---
# ๐ง MODULARIZE USER'S MANUAL ๐ง
## <cyan>Modularize Usage</cyan>
modularize [<modularize-options>] [<module-map>|<include-files-list>]* [<front-end-options>...]
`<modularize-options>` is a place-holder for options specific to `modularize`, which are described below in *Modularize Command Line Options*.
`<module-map>` specifies the path of a file name for an existing module map. The module map must be well-formed in terms of syntax. `Modularize` will extract the header file names from the map. Only normal headers are checked, assuming headers marked `"private"`, `"textual"`, or `"exclude"` are not to be checked as a top-level include, assuming they either are included by other headers which are checked, or they are not suitable for modules. ๐
`<include-files-list>` specifies the path of a file name for a file containing the newline-separated list of headers to check with respect to each other. Lines beginning with `'#'` and empty lines are ignored. Header file names followed by a colon and other space-separated file names will include those extra files as dependencies. The file names can be relative or full paths, but must be on the same line. For example:
header1.h header2.h header3.h: header1.h header2.h
Note that unless a `-prefix` (header path) option is specified, non-absolute file paths in the header list file will be relative to the header list file directory. Use `-prefix` to specify a different directory. ๐ธ
`<front-end-options>` is a place-holder for regular Clang front-end arguments, which must follow the `<include-files-list>`. Note that by default, `modularize` assumes `.h` files contain C++ source, so if you are using a different language, you might need to use a `-x` option to tell Clang that the header contains another language, i.e.: `-x c`
Note also that because `modularize` does not use the clang driver, you will likely need to pass in additional compiler front-end arguments to match those passed in by default by the driver. ๐
---
## ๐ Modularize Command Line Options ๐
- **`-prefix=<header-path>`** โ Prepend the 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. Use `-prefix` to specify 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. 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-path` option 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-block check 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-lists` option, 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. ๐
`modularize` also has an assistant mode option for generating a module map file based on the provided header list. The generated file is a functional module map that can be used as a starting point for a `module.modulemap` file. โจ
---
## โญ Getting Started โญ
To build from source:
1. Go read <pink>**Getting Started with the LLVM System**</pink> and <pink>**Clang Tools Documentation**</pink> bestie, they've got the whole scoop on where to grab sources for LLVM, Clang, and Clang Extra Tools. ๐
2. <pink>**Getting Started with the LLVM System**</pink> and <pink>**Building LLVM with CMake**</pink> will walk you through how to actually build the thing. Once your sources are all checked out in the right place, the LLVM build will build Clang Extra Tools and their dependencies automatically. no cap, it just does it. โจ
- If you're on that CMake life, you can also use the `modularize` target to build <purple>just</purple> the modularize tool and its dependencies. slay.
Before you keep going, go peek at <pink>**Modularize Usage**</pink> to see how to actually invoke `modularize`. ๐
---
## โจ What Modularize Checks โจ
Okay so `modularize` is gonna vibe check the following:
- Duplicate global type and variable definitions ๐
- Duplicate macro definitions ๐
- Macro instances, `'defined(macro)'`, or `#if`, `#elif`, `#ifdef`, `#ifndef` conditions that evaluate differently in a header
- `#include` directives inside `'extern "C/C++" {}'` or `'namespace (name) {}'` blocks
- Module map header coverage completeness (but only when the input is a module map, iykyk)
`modularize` does normal C/C++ parsing and reports normal errors and warnings, but it's also gonna 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
And that second one might get followed up with messages like:
note: '(symbol)' in (file) at (row):(column) not always provided
It also checks macro expansions, `defined(macro)` expressions, and preprocessor conditional directives that evaluate inconsistently โ and those can throw error messages like this absolute unit:
(...)/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.
And it checks `'#include'` directives that are nested inside `'extern "C/C++" {}'` or `'namespace (name) {}'` blocks too, producing error messages like:
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 ๐ธ
This 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 <purple>all</purple> the files it considers headers (no extension, `.h`, or `.inc` โ and if you need more than that, go modify the `isHeader` function yourself, bestie). Then it compares those headers against the ones referenced in the module map, whether they're explicitly named or implicitly named via an umbrella directory or umbrella file, as parsed by the `ModuleMap` object. ๐ฆ
If headers turn up that aren't referenced or covered by an umbrella directory or file, you get warning messages and this program returns an <hotpink>error code of `1`</hotpink>. If everything's clean? You get an error code of `0`. periodt.
> Heads up: for 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 put them on the command line after the module map file argument. ๐ซถ
Warning messages look like this:
warning: module.modulemap does not account for file: Level3A.h
And note that when the module map references a file that doesn't exist, the module map parser in Clang will (at the time of this writing) show you an error message. ๐ญ
Wanna limit `modularize` to <yellow>just</yellow> the module map coverage check? Use the `-coverage-check-only` option. Easy.
Like, for example:
modularize -coverage-check-only module.modulemap
---
## ๐
Module Map Generation ๐
If you pass `-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 if a header in the header list is a partial path, a nested module hierarchy gets created โ a module 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 those same modules too. ๐
So like, for this header list:
SomeTypes.h SomeDecls.h SubModule1/Header1.h SubModule1/Header2.h SubModule2/Header3.h SubModule2/Header4.h SubModule2.h
You get this module map 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 that makes a root module get created, and that root module encloses all the modules. main character energy fr. ๐
And an optional `-problem-files-list=<problem-file-name>` can be used to input a list of files to be excluded โ maybe as a temporary stop-gap measure until those problem headers get fixed. ๐
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 * } }
> Note that headers with dependents get ignored with a warning, because the Clang module mechanism doesn't support headers that rely on other headers being included first. it's giving limitation, but that's the deal. ๐
Also the module map format defines some keywords that can't be used in module names. If a header has one of these names, an underscore (`'_'`) gets prepended to the name. So like, if the header name is `header.h`, then because `header` is a keyword, the module name will be `_header`. For a list of the module map keywords, please see: <pink>**Lexical structure**</pink>. โญ
# ๐ PP-TRACE USER'S MANUAL ๐
<pink>okay bestie, meet</pink> `pp-trace` โ a standalone tool that traces preprocessor activity. It's also used as a test of Clang's `PPCallbacks` interface. What it does is run a given source file through the Clang preprocessor, showing you selected info from callback functions that get overridden in a `PPCallbacks` derivation. The output? High-level YAML, described below 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 a place-holder for options specific to `pp-trace`, which are described below in *Command Line Options*.
- `<source-file>` specifies the source file to run through the preprocessor.
- `<front-end-options>` is a place-holder for regular Clang Compiler Options, which must follow the `<source-file>`.
---
### ๐ Command Line Options ๐
- **`-callbacks <comma-separated-globs>`** โ This option specifies a comma-separated list of globs describing the list of callbacks that should be traced. Globs are processed in order of appearance. Positive globs add matched callbacks to the set, negative globs (those with the `-` prefix) remove callacks from the set.
- `FileChanged`
- `FileSkipped`
- `InclusionDirective`
- `moduleImport`
- `EndOfMainFile`
- `Ident`
- `PragmaDirective`
- `PragmaComment`
- `PragmaDetectMismatch`
- `PragmaDebug`
- `PragmaMessage`
- `PragmaDiagnosticPush`
- `PragmaDiagnosticPop`
- `PragmaDiagnostic`
- `PragmaOpenCLExtension`
- `PragmaWarning`
- `PragmaWarningPush`
- `PragmaWarningPop`
- `MacroExpands`
- `MacroDefined`
- `MacroUndefined`
- `Defined`
- `SourceRangeSkipped`
- `If`
- `Elif`
- `Ifdef`
- `Ifndef`
- `Else`
- `Endif`
- **`-output <output-file>`** โ By default, `pp-trace` outputs the trace information to stdout. Use this option to output the trace information to a file.
---
## ๐ฆ pp-trace Output Format ๐ฆ
The `pp-trace` output is formatted as YAML. See <cyan>https://yaml.org/</cyan> for general YAML information. It's arranged as a sequence of information about the callback call, including the callback name and argument information, for example:
- Callback: Name Argument1: Value1 Argument2: Value2 (etc.) ...
With real data:
- 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 same name as the argument in the corresponding `PPCallbacks` callback function. ๐
---
## ๐ Callback Details ๐
The following sections describe the purpose and output format for each callback.
Click on the callback name in the section heading to see the Doxygen documentation for the callback.
The argument descriptions table describes the callback argument information 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. Enumeration member symbols are shown as the actual enum member in a `(member1|member2|...)` form. A name in parentheses can either represent a place holder for the described value, or confusingly, it might be a literal, such as `(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 from the callback function declaration.
The description describes the argument or what is displayed for it.
> Note that in some cases, such as 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 all members of the structure. ๐ซถ
---
## ๐ FileChanged Callback ๐
`FileChanged` is called when the preprocessor enters or exits a file, both the top level file being compiled, as well as any `#include` directives. It will also be called as a result of a system header pragma or in internal renaming of a file.
Argument descriptions:
- **`Loc`**
- Argument Value Syntax: `"(file):(line):(col)"`
- Clang C++ Type: `SourceLocation`
- Description: The location of the directive.
- **`Reason`**
- Argument Value Syntax: `(EnterFile|ExitFile|SystemHeaderPragma|RenameFile)`
- Clang C++ Type: `PPCallbacks::FileChangeReason`
- Description: Reason for change.
- **`FileType`**
- Argument Value Syntax: `(C_User|C_System|C_ExternCSystem)`
- Clang C++ Type: `SrcMgr::CharacteristicKind`
- Description: Include type.
- **`PrevFID`**
- Argument Value Syntax: `((file)|(invalid))`
- Clang C++ Type: `FileID`
- Description: Previous file, if 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 ๐
`FileSkipped` is called when a source file is skipped as the result of header guard optimization.
Argument descriptions:
- **`ParentFile`**
- Argument Value Syntax: `("(file)" or (null))`
- Clang C++ Type: `const FileEntry`
- Description: The file that `#included` the skipped file.
- **`FilenameTok`**
- Argument Value Syntax: `(token)`
- Clang C++ Type: `const Token`
- Description: The token in `ParentFile` that indicates the skipped file.
- **`FileType`**
- Argument Value Syntax: `(C_User|C_System|C_ExternCSystem)`
- Clang C++ Type: `SrcMgr::CharacteristicKind`
- Description: The file type.
Example:
- Callback: FileSkipped ParentFile: "/path/filename.h" FilenameTok: "filename.h" FileType: C_User
## InclusionDirective Callback
<pink>okay bestie this one is a big deal ๐</pink> <purple>`InclusionDirective`</purple> fires whenever an inclusion directive of <hotpink>any</hotpink> kind has been processed โ that's `#include`, `#import`, all of them โ and the important little twist is that it fires *regardless* of whether the inclusion will actually result in an inclusion. So no skipping, no "oh it didn't work so we won't tell you". You get told. Periodt. โจ
Argument descriptions, and yes, all of them:
- **`HashLoc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. This is the location of the `'#'` that starts the inclusion directive. ๐ธ
- **`IncludeTok`** โ value syntax `(token)`, Clang C++ type `const Token`. The token that indicates the kind of inclusion directive, e.g., `'include'` or `'import'`.
- **`FileName`** โ value syntax `"(file)"`, Clang C++ type `StringRef`. The name of the file being included, as written in the source code. (as written, bestie, not as resolved โ that matters ๐)
- **`IsAngled`** โ value syntax `(true|false)`, Clang C++ type `bool`. Whether the file name was enclosed in angle brackets; otherwise, it was enclosed in quotes.
- **`FilenameRange`** โ value syntax `"(file)"`, Clang C++ type `CharSourceRange`. The character range of the quotes or angle brackets for the written file name. ๐
- **`File`** โ value syntax `"(file)"`, Clang C++ type `const FileEntry`. The actual file that may be included by this inclusion directive.
- **`SearchPath`** โ value syntax `"(path)"`, Clang C++ type `StringRef`. Contains the search path which was used to find the file in the file system.
- **`RelativePath`** โ value syntax `"(path)"`, Clang C++ type `StringRef`. The path relative to `SearchPath`, at which the include file was found.
- **`Imported`** โ value syntax `((module name)|(null))`, Clang C++ type `const Module`. The module, whenever an inclusion directive was automatically turned into a module import or null otherwise. ๐ฆ
Example::
- 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
<cyan>`moduleImport`</cyan> is called when there was an explicit module-import syntax. Like, you actually typed the import. It's giving "I meant to do that" ๐
Argument descriptions:
- **`ImportLoc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. The location of import directive token.
- **`Path`** โ value syntax `"(path)"`, Clang C++ type `ModuleIdPath`. The identifiers (and their locations) of the module `"path"`.
- **`Imported`** โ value syntax `((module name)|(null))`, Clang C++ type `const Module`. The imported module; can be null if importing failed. ๐ญ
Example::
- 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
<lavender>`EndOfMainFile`</lavender> is called when the end of the main file is reached. That's it, that's the whole vibe โ the main file is done, the callback says so. ๐
**Argument descriptions** โ and bestie, this one's an empty table, which is honestly so valid of it ๐
:
- **Argument Name** โ `(no arguments)`
- **Argument Value Syntax** โ *(nothing, it's giving minimalist)*
- **Clang C++ Type** โ *(also nothing)*
- **Description** โ *(still nothing, she's shy)*
Example::
- Callback: EndOfMainFile
---
### ๐ธ Ident Callback
`Ident` gets called when a `#ident` or `#sccs` directive is read. Like, she shows up the moment those directives hit the scene โจ
**Argument descriptions:**
- **`Loc`** โ value syntax is `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ it's the location of the directive ๐
- **`str`** โ value syntax is `(name)`, Clang C++ type `const std::string` โ 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
`PragmaDirective` gets called when it starts reading *any* pragma directive. Every single one, no exceptions, she's thorough ๐ซถ
**Argument descriptions:**
- **`Loc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ the location of the directive ๐
- **`Introducer`** โ value syntax `(PIK_HashPragma|PIK__Pragma|PIK___pragma)`, Clang C++ type `PragmaIntroducerKind` โ the type of the pragma directive ๐
Example::
- Callback: PragmaDirective Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1" Introducer: PIK_HashPragma
---
### ๐ PragmaComment Callback
`PragmaComment` gets called when a `#pragma comment` directive is read. Comment pragma girlies rise up ๐
**Argument descriptions:**
- **`Loc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ the location of the directive ๐
- **`Kind`** โ value syntax `((name)|(null))`, Clang C++ type `const IdentifierInfo` โ the comment kind symbol ๐ค
- **`Str`** โ value syntax `(message directive)`, Clang C++ type `const std::string` โ the comment message directive ๐ฌ
Example::
- 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` gets called when a `#pragma detect_mismatch` directive is read. Yes she caught that mismatch, she's observant like that ๐
**Argument descriptions:**
- **`Loc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ the location of the directive ๐
- **`Name`** โ value syntax `"(name)"`, Clang C++ type `const std::string` โ the name ๐ท๏ธ
- **`Value`** โ value syntax `(string)`, Clang C++ type `const 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` gets called when a `#pragma clang __debug` directive is read. Debug mode activated, no cap ๐
**Argument descriptions:**
- **`Loc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ the location of the directive ๐
- **`DebugType`** โ value syntax `(string)`, Clang C++ type `StringRef` โ indicates the 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` gets called when a `#pragma message` directive is read. A little message just for you, bestie ๐
**Argument descriptions:**
okay so the `PragmaMessage` callback sends its args in this exact little grid, and honestly? it's giving spreadsheet realness ๐โจ โ here's the whole tea, untouched:
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ โ Argument Name โ Argument Value โ Clang C++ Type โ Description โ โ โ Syntax โ โ โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค โ Loc โ "(file):(line):(col)" โ SourceLocation โ The location of โ โ โ โ โ the directive. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค โ Namespace โ (name) โ StringRef โ The namespace of โ โ โ โ โ the message โ โ โ โ โ directive. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค โ Kind โ (PMK_Message|PMK_Warning|PMK_Error) โ PPCallbacks::PragmaMessageKind โ The type of the โ โ โ โ โ message โ โ โ โ โ directive. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค โ Str โ (string) โ StringRef โ The text of the โ โ โ โ โ message โ โ โ โ โ directive. โ โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโ
translating that for the girlies ๐
:
- **`Loc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. It's <pink>the location of the directive</pink>. ๐ where the drama went down.
- **`Namespace`** โ value syntax `(name)`, type `StringRef`. <lavender>The namespace of the message directive</lavender>.
- **`Kind`** โ value syntax `(PMK_Message|PMK_Warning|PMK_Error)`, type `PPCallbacks::PragmaMessageKind`. <yellow>The type of the message directive</yellow>. so likeโฆ is it a vibe, a warning, or a full-on error bestie ๐ญ
- **`Str`** โ value syntax `(string)`, type `StringRef`. <mint>The text of the message directive</mint>. the actual words, fr fr.
and here's what it looks like when it actually fires ๐
- 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` shows up the second a `#pragma gcc diagnostic push` directive gets read. yes, the *push*. iconic behaviour, saving your diagnostic state like a lil bookmark ๐
Argument descriptions, in their full glory:
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ โ Argument Name โ Argument Value โ Clang C++ Type โ Description โ โ โ Syntax โ โ โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ Loc โ "(file):(line):(col)" โ SourceLocation โ The location of โ โ โ โ โ the directive. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ Namespace โ (name) โ StringRef โ Namespace name. โ โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโ
- **`Loc`** โ value syntax `"(file):(line):(col)"`, type `SourceLocation`. <pink>The location of the directive</pink>. ๐ฏ
- **`Namespace`** โ value syntax `(name)`, type `StringRef`. <lavender>Namespace name</lavender>. periodt.
Example time, no notes ๐:
- Callback: PragmaDiagnosticPush Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1" Namespace: "GCC"
---
## ๐ธ PragmaDiagnosticPop Callback
`PragmaDiagnosticPop` is the other half of the duo โ it gets called when a `#pragma gcc diagnostic pop` directive is read. push and pop, they're literally the main characters together ๐๐
Argument descriptions:
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ โ Argument Name โ Argument Value โ Clang C++ Type โ Description โ โ โ Syntax โ โ โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ Loc โ "(file):(line):(col)" โ SourceLocation โ The location of โ โ โ โ โ the directive. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ Namespace โ (name) โ StringRef โ Namespace name. โ โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโ
- **`Loc`** โ value syntax `"(file):(line):(col)"`, type `SourceLocation`. <pink>The location of the directive</pink>. ๐
- **`Namespace`** โ value syntax `(name)`, type `StringRef`. <lavender>Namespace name</lavender>. same energy as its push sibling, love that for them.
Example, slay:
- Callback: PragmaDiagnosticPop Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1" Namespace: "GCC"
---
## ๐ฆ PragmaDiagnostic Callback
`PragmaDiagnostic` gets called when a `#pragma gcc diagnostic` directive is read โ the plain one, no push, no pop, just raw directive energy โก
Argument descriptions:
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ โ Argument Name โ Argument Value โ Clang C++ Type โ Description โ โ โ Syntax โ โ โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ Loc โ "(file):(line):(col)" โ SourceLocation โ The location of โ โ โ โ โ the directive. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ Namespace โ (name) โ StringRef โ Namespace name. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ mapping โ (0|MAP_IGNORE|MAP_WARNING|MAP_ERROR|MAP_FATAL) โ diag::Severity โ Mapping type. โ โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค โ Str โ (string) โ StringRef โ Warning/error โ โ โ โ โ name. โ
so real quick, field by field bestie ๐:
- **`Loc`** โ value syntax `"(file):(line):(col)"`, type `SourceLocation`. <pink>The location of the directive</pink>. ๐บ๏ธ
- **`Namespace`** โ value syntax `(name)`, type `StringRef`. <lavender>Namespace name</lavender>.
- **`mapping`** โ value syntax `(0|MAP_IGNORE|MAP_WARNING|MAP_ERROR|MAP_FATAL)`, type `diag::Severity`. <hotpink>Mapping type</hotpink>. this one decides whether a diagnostic is ignored, a warning, an error, or straight-up fatal โ highkey the mood-setter of the whole directive ๐ฅ
- **`Str`** โ value syntax `(string)`, type `StringRef`. <mint>Warning/error name</mint>. it's the name of the thing being mapped, iykyk.
and there's still more where this came from, so don't go anywhere ๐
- 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
---
### <pink>PragmaOpenCLExtension</pink> Callback โจ
<purple>PragmaOpenCLExtension</purple> fires whenever an OpenCL extension gets either <hotpink>disabled</hotpink> or <mint>enabled</mint> via a pragma. bestie it literally cannot make up its mind and we're here for it ๐
**Argument descriptions:**
- **`NameLoc`** โ argument value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. It's the location of the name. ๐
- **`Name`** โ argument value syntax `(name)`, Clang C++ type `const IdentifierInfo`. Name symbol.
- **`StateLoc`** โ argument value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. The location of the state.
- **`State`** โ argument value syntax `(1|0)`, Clang C++ type `unsigned`. Enabled/disabled state. (`1` = on, `0` = off, the binary of it all ๐ฆ)
**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
---
### <pink>PragmaWarning</pink> Callback ๐
<purple>PragmaWarning</purple> gets called the moment a `#pragma warning` directive is read. okay so the preprocessor said "warning" and pp-trace said "noted ๐".
**Argument descriptions:**
- **`Loc`** โ argument value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. The location of the directive. ๐
- **`WarningSpec`** โ argument value syntax `(string)`, Clang C++ type `StringRef`. The warning specifier.
- **`Ids`** โ argument value syntax `[(number)[, ...]]`, Clang C++ type `ArrayRef<int>`. The warning numbers. a whole lil squad of them fr fr ๐ข
**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
---
### <pink>PragmaWarningPush</pink> Callback ๐ซถ
<purple>PragmaWarningPush</purple> is the callback for when a `#pragma warning(push)` directive is read. it's giving "save my warning settings real quick" energy ๐พ
**Argument descriptions:**
- **`Loc`** โ argument value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. The location of the directive. ๐
- **`Level`** โ argument value syntax `(number)`, Clang C++ type `int`. Warning level.
**Example:**
- Callback: PragmaWarningPush Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1" Level: 1
---
### <pink>PragmaWarningPop</pink> Callback ๐ธ
<purple>PragmaWarningPop</purple> gets called when a `#pragma warning(pop)` directive is read. yes queen, restore those settings ๐
**Argument descriptions:**
- **`Loc`** โ argument value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation`. The location of the directive. ๐
**Example:**
- Callback: PragmaWarningPop Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
---
### <pink>MacroExpands</pink> Callback ๐
<purple>MacroExpands</purple> is called when `::HandleMacroExpandedIdentifier` happens โ aka when a macro invocation is found. this is THE moment the macro girlies have been waiting for ๐ญ๐ฅ
**Argument descriptions:**
- **`MacroNameTok`** โ argument value syntax `(token)`, Clang C++ type `const Token`. The macro name token.
- **`MacroDirective`** โ argument value syntax `(MD_Define|MD_Undefine|MD_Visibility)`, Clang C++ type `const MacroDirective`. The kind of macro directive from the `MacroDirective` structure.
- **`Range`** โ argument value syntax `["(file):(line):(col)", "(file):(line):(col)"]`, Clang C++ type `SourceRange`. The source range for the expansion.
<purple>ok bestie last row of the `MacroExpands` arg table, then we move on, stay with me ๐
</purple>
- **Argument Name:** `Args`
- **Argument Value Syntax:** `[(name)|(number)|<(token name)>[, ...]]`
- **Clang C++ Type:** `const MacroArgs`
- **Description:** the argument tokens. Names and numbers are literal, everything else is of the form `'<' tokenName '>'`.
<pink>and here's what that actually looks like in the wild, no cap โจ</pink>
- Callback: MacroExpands MacroNameTok: X_IMPL MacroDirective: MD_Define Range: [(nonfile), (nonfile)] Args: [a <plus> y, b]
---
## <hotpink>MacroDefined Callback ๐</hotpink>
<lavender>`MacroDefined` is called when a macro definition is seen.</lavender>
<purple>the argument breakdown, served fresh:</purple>
- **Argument Name:** `MacroNameTok`
- **Argument Value Syntax:** `(token)`
- **Clang C++ Type:** `const Token`
- **Description:** the macro name token.
- **Argument Name:** `MacroDirective`
- **Argument Value Syntax:** `(MD_Define|MD_Undefine|MD_Visibility)`
- **Clang C++ Type:** `const MacroDirective`
- **Description:** the kind of macro directive from the `MacroDirective` structure.
<pink>example time, look at her go:</pink>
- Callback: MacroDefined MacroNameTok: X_IMPL MacroDirective: MD_Define
---
## <hotpink>MacroUndefined Callback ๐ฆ</hotpink>
<lavender>`MacroUndefined` is called when a macro `#undef` is seen.</lavender>
<cyan>same energy, same shape, here's the tea:</cyan>
- **Argument Name:** `MacroNameTok`
- **Argument Value Syntax:** `(token)`
- **Clang C++ Type:** `const Token`
- **Description:** the macro name token.
- **Argument Name:** `MacroDirective`
- **Argument Value Syntax:** `(MD_Define|MD_Undefine|MD_Visibility)`
- **Clang C++ Type:** `const MacroDirective`
- **Description:** the kind of macro directive from the `MacroDirective` structure.
<pink>example, verbatim, don't @ me:</pink>
- Callback: MacroUndefined MacroNameTok: X_IMPL MacroDirective: MD_Define
---
## <hotpink>Defined Callback ๐</hotpink>
<lavender>`Defined` is called when the `'defined'` operator is seen.</lavender>
<purple>three whole arguments this time, main character energy fr fr:</purple>
- **Argument Name:** `MacroNameTok`
- **Argument Value Syntax:** `(token)`
- **Clang C++ Type:** `const Token`
- **Description:** the macro name token.
- **Argument Name:** `MacroDirective`
- **Argument Value Syntax:** `(MD_Define|MD_Undefine|MD_Visibility)`
- **Clang C++ Type:** `const MacroDirective`
- **Description:** the kind of macro directive from the `MacroDirective` structure.
- **Argument Name:** `Range`
- **Argument Value Syntax:** `["(file):(line):(col)", "(file):(line):(col)"]`
- **Clang C++ Type:** `SourceRange`
- **Description:** the source range for the directive.
and here's what that looks like in the wild ๐ฆ:
- 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` Callback โจ
ok so `SourceRangeSkipped` is literally the callback that yells at you the second a source range gets **skipped**. that's it, that's the whole vibe ๐
**Argument descriptions:**
- **`Range`** โ value syntax `["(file):(line):(col)", "(file):(line):(col)"]`, Clang C++ type `SourceRange` โ the source range that was skipped. gone. not read. ๐ซถ
**Example:**
- 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` Callback ๐ธ
`If` (yes capital I, lowercase f, that's her name, don't @ me) gets called when an `#if` is seen ๐
**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 condition.
- **`ConditionValue`** โ value syntax `(true|false)`, Clang C++ type `bool` โ the condition value.
**Example:**
- 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` Callback ๐
`Elif` shows up when an `#elif` is seen โ the "okay but what about *this* case" of the preprocessor world ๐
**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 condition.
- **`ConditionValue`** โ value syntax `(true|false)`, Clang C++ type `bool` โ the condition value.
- **`IfLoc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ the location of the directive.
**Example:**
- 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` Callback ๐
`Ifdef` is called when an `#ifdef` is seen. iconic behavior, no notes โญ
**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 the `MacroDirective` structure.
**Example:**
- Callback: Ifdef Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-conditional.cpp:3:1" MacroNameTok: MACRO MacroDirective: MD_Define
---
### ๐ฆ `Ifndef` Callback ๐ฆ
`Ifndef` is called when an `#ifndef` is seen. the "just in case nobody defined it yet" queen ๐
**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 is `(MD_Define|MD_Undefine|MD_Visibility)`, Clang C++ type `const MacroDirective`, and it's literally "The kind of macro directive from the `MacroDirective` structure." ๐
Example, bestie:
- 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 ๐ฆ
`Else` gets called the moment an `#else` shows up. she's always in her feels about it ๐ญ
Argument descriptions:
- **`Loc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ <pink>the location of the `else` directive.</pink>
- **`IfLoc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ <pink>the location of the `if` directive.</pink>
Example:
- 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 โจ
`Endif` is the one that shows up when an `#endif` gets seen. closing the vibe, periodt ๐
Argument descriptions:
- **`Loc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ <lavender>the location of the `endif` directive.</lavender>
- **`IfLoc`** โ value syntax `"(file):(line):(col)"`, Clang C++ type `SourceLocation` โ <lavender>the location of the `if` directive.</lavender>
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 ๐
Wanna build from source? ok so, step by step:
1. Read <cyan>Getting Started with the LLVM System</cyan> and <cyan>Clang Tools Documentation</cyan> for info on how to get sources for LLVM, Clang, and Clang Extra Tools. fr fr, don't skip this.
2. <cyan>Getting Started with the LLVM System</cyan> and <cyan>Building LLVM with CMake</cyan> give you the directions for how to build. With all the sources checked out in the right place, the LLVM build will build Clang Extra Tools and their dependencies automatically. slay.
- If you're using CMake, you can also use the `pp-trace` target to build just the `pp-trace` tool and its dependencies. iconic.
---
# ๐ 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. yes, from the source AND the comments, she reads everything ๐โจ
The tool is in a <yellow>very early development stage</yellow>, so you might encounter bugs and crashes. Submitting reports with information about how to reproduce the issue to the LLVM bug tracker will definitely help the project. If you have any ideas or suggestions, please put a feature request there. bestie, your bug reports are literally a gift ๐ซถ
## ๐ธ Use ๐ธ
`clang-doc` is a LibTooling-based tool, and so requires a compile command database for your project (for an example of how to do this see <cyan>How To Setup Tooling For LLVM</cyan>).
By default the tool will process a list of files, like:
$ 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. it's giving organized ๐
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. so rn it's flags only, no cap.
## โญ 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-hidden for more) -help-list - Display list of available options (-help-list-hidden for 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.
> Heads up bestie: the following flags should <hotpink>only</hotpink> be used if `format` is set to `html`: `-repository`, `-source-root`, `-stylesheets`. don't be out here using them wrong ๐
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
> **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 <red>out of date!</red> ๐ญ
- Index
- Search Page
---
# ๐ AUTHOR ๐
The Clang Team. absolute icons ๐
# ๐ COPYRIGHT ๐
2007-2025, The Clang Team.
---
and that's a wrap on `EXTRACLANGTOOLS(1)`, bestie!! ๐โจ this whole thing is page 20, dated Sep 03, 2025, and yes it's the `EXTRACLANGTOOLS(1)` manual โ you ate that, go touch some grass now ๐ฆ๐
๐ฆ where this page comes from
- fedora 43: clang20-tools-extra 20.1.8-2.fc43 ยท Apache-2.0 WITH LLVM-exception OR NCSA
- fedora 43: clang20-tools-extra 20.1.8-2.fc43 ยท Apache-2.0 WITH LLVM-exception OR NCSA
- fedora 44: clang20-tools-extra 20.1.8-2.fc44 ยท Apache-2.0 WITH LLVM-exception OR NCSA
- fedora 44: clang20-tools-extra 20.1.8-2.fc44 ยท Apache-2.0 WITH LLVM-exception OR NCSA
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms