EXTRACLANGTOOLS(1)

๐Ÿ’… EXTRACLANGTOOLS โ€” the extra clang tools glow-up

okay so bestie, welcome to extraclangtools โ€” this is the manual page for the clang-tools-extra project, aka the little bundle of extra tools built on top of Clang's tooling APIs. It's giving "Clang but with accessories." โœจ


๐Ÿ’– NAME ๐Ÿ’–

extraclangtools โ€” Extra Clang Tools Documentation

Welcome to the clang-tools-extra project which contains extra tools built using Clang's tooling APIs. ๐Ÿซถ


๐ŸŒˆ EXTRA CLANG TOOLS 23.1.3 RELEASE NOTES ๐ŸŒˆ

Here's the whole table of contents, no gatekeeping, we're listing everything:

  • Introduction
  • What's New in Extra Clang Tools 23.1.3?
    • Major New Features
    • Potentially Breaking Changes
    • Improvements to clangd
      • Compile flags
      • Diagnostics
      • Clang-tidy integration
      • Hover
      • Code completion
      • Cross-references
      • Inlay hints
      • Code actions
      • Semantic Highlighting
      • Call hierarchy
      • Remote index
      • Improvements to 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 literally the release notes for the Extra Clang Tools, which is part of the Clang release 23.1.3. We're describing the status of the Extra Clang Tools in some detail here โ€” the major improvements since the last release, plus all the new feature work. Lowkey thorough. ๐Ÿ’…

All LLVM releases may be downloaded from the LLVM releases web site.

For more information about Clang or LLVM, including info about the latest release, please see the Clang Web Site or the LLVM Web Site. ๐Ÿฆ‹

โš ๏ธ real talk: if you're reading this file from a Git checkout or the main Clang web page, this document applies to the next release, not the current one. If you want the release notes for a specific release, see the releases page. Don't get caught out bestie ๐Ÿ˜ญ

๐ŸŽ€ What's New in Extra Clang Tools 23.1.3? ๐ŸŽ€

Some of the major new features and improvements to Extra Clang Tools are listed here. The generic improvements to Extra Clang Tools as a whole โ€” or to its underlying infrastructure โ€” come first, and then we get into the tool-specific sections. Order matters, slay. โœจ


โญ Major New Features โญ

(nothing listed here yet โ€” it's just the heading holding space, main character energy ๐Ÿซถ)


๐Ÿ’€ Potentially Breaking Changes ๐Ÿ’€

Okay heads up, these ones actually affect you:

  • The modernize-use-using check now sets the IgnoreExternC option to true by default. So the check will no longer transform typedefs within extern "C" blocks. If you were relying on that, adjust your vibes accordingly ๐Ÿ™ˆ
  • Deprecated the clang-tidy check performance-faster-string-find. It has been renamed to performance-prefer-single-char-overloads. The original check will be removed in the 25th release โ€” so like, start migrating now, not later ๐Ÿ˜ญ
  • Removed the clang-tidy hicpp module. All checks have been moved to the other modules. Use the replacement checks instead ๐Ÿ‘‡
  • hicpp-avoid-c-arrays โ†’ modernize-avoid-c-arrays
  • hicpp-avoid-goto โ†’ cppcoreguidelines-avoid-goto
  • hicpp-braces-around-statements โ†’ readability-braces-around-statements
  • hicpp-deprecated-headers โ†’ modernize-deprecated-headers
  • hicpp-exception-baseclass โ†’ bugprone-std-exception-baseclass
  • hicpp-explicit-conversions โ†’ misc-explicit-constructor
  • hicpp-function-size โ†’ readability-function-size
  • hicpp-ignored-remove-result โ†’ bugprone-unused-return-value
  • hicpp-invalid-access-moved โ†’ bugprone-use-after-move
  • hicpp-member-init โ†’ cppcoreguidelines-pro-type-member-init
  • hicpp-move-const-arg โ†’ performance-move-const-arg
  • hicpp-multiway-paths-covered โ†’ bugprone-unhandled-code-paths, readability-trivial-switch
  • hicpp-named-parameter โ†’ readability-named-parameter
  • hicpp-new-delete-operators โ†’ misc-new-delete-overloads
  • hicpp-no-array-decay โ†’ cppcoreguidelines-pro-bounds-array-to-pointer-decay
  • hicpp-no-assembler โ†’ portability-no-assembler
  • hicpp-no-malloc โ†’ cppcoreguidelines-no-malloc
  • hicpp-noexcept-move โ†’ performance-noexcept-move-constructor
  • hicpp-signed-bitwise โ†’ bugprone-signed-bitwise
  • hicpp-special-member-functions โ†’ cppcoreguidelines-special-member-functions
  • hicpp-static-assert โ†’ misc-static-assert
  • hicpp-uppercase-literal-suffix โ†’ readability-uppercase-literal-suffix
  • hicpp-undelegated-constructor โ†’ bugprone-undelegated-constructor
  • hicpp-use-auto โ†’ modernize-use-auto
  • hicpp-use-emplace โ†’ modernize-use-emplace

(and yes, hicpp-multiway-paths-covered gets two replacements โ€” it's just built different ๐Ÿ‘‘)


  • hicpp-use-equals-default โ†’ modernize-use-equals-default
  • hicpp-use-equals-delete โ†’ modernize-use-equals-delete
  • hicpp-use-noexcept โ†’ modernize-use-noexcept
  • hicpp-use-nullptr โ†’ modernize-use-nullptr
  • hicpp-use-override โ†’ modernize-use-override
  • hicpp-vararg โ†’ cppcoreguidelines-pro-type-vararg

Okay so real talk, the clang-doc girlies took a couple of L's this cycle and we need to talk about it ๐Ÿ˜ญ

๐Ÿซถ Heads up on clang-doc removals: - The deprecated Markdown generator in clang-doc? Gone. --format=md now routes through the shiny new Mustache-backed MD generator instead. Fair warning bestie: it's possible there are some regressions in MD output. ๐Ÿ’€ - The YAML generator in clang-doc is also removed. If you want documentation info in a reusable format, the JSON generator is the one to use now. โœจ

๐Ÿ’– Improvements to clangd ๐Ÿ’–

๐ŸŽ€ Compile flags

  • The clang-cl flags /std:c++23preview and /std:clatest are recognized now โ€” yes, both of them, we love to see it ๐Ÿ‘‘
  • The logic for finding the resource directory that holds clang's builtin headers got a glow-up, so it now handles the situation where clangd and clang are installed in different prefixes. No cap, that used to be a whole mess.
  • Environment variables that influence compilation get logged on startup ๐Ÿชต โ€” so you can actually see what's steering your build.

๐Ÿฉบ Diagnostics

  • Driver diagnostics can be suppressed via config ๐Ÿซถ
  • pragma diagnostic push/pop is handled correctly across a preamble boundary โ€” iconic behaviour, honestly.
  • Unknown compiler options are dropped instead of causing a fatal error ๐Ÿ’€ (we simply do not panic in this house)

๐Ÿงน Clang-tidy integration

  • bugprone-bad-signal-to-kill-thread now works with clangd ๐ŸŽ€
  • Clang-tidy checks can properly see macro definitions located in the preamble region at the top of the file โ€” yes bestie, the top-of-file macros are visible now.

๐Ÿ–ฑ๏ธ Hover

  • Hovering over an accessor method shows you the comment for the accessed field ๐Ÿ‘€ obsessed
  • Improved handling of unknown doxygen commands
  • Fixed a regression in whitespace escaping behaviour when processing documentation comments (we do not talk about the regression)

โŒจ๏ธ Code completion

  • Clangd recognizes when you're completing a definition of a function โ€” as opposed to a call โ€” and gives you dedicated behaviour that's actually suitable for definitions โœจ
  • It now also provides include files without an extension, if they live in a directory called only include.
  • Added support for InsertReplaceEdit in code completion (LSP 3.16) โ€” so clients that advertise insertReplaceSupport get both insert and replace ranges for completion items. Slay.
  • Improved logic to avoid inserting a redundant parenthesis when you're completing in the middle of an identifier ๐Ÿ™ˆ
  • Completion-style default changed to detailed. Okay so this is a big one: function overloads are no longer bundled together โ€” each one gets its own completion item. That gives you a better overview of the possible overloads, and when you accept the item it generates placeholder parameters, which literally wasn't possible before because of the ambiguity with bundled. Want the old behaviour back? Pass --completion-style=bundled to clangd. ๐Ÿ’…
  • The ArgumentLists config option is respected for lambdas ๐Ÿซถ
  • Code completion for field names in __builtin_offsetof expressions
  • Clangd tries harder to find parameter names to show during code completion โ€” it'll look in other redeclarations of a function if it has to. The dedication ๐ŸคŒ

๐Ÿ”— Cross-references

  • Code navigation works correctly on tokens inside attributes, __builtin_offsetof expressions, and member pointers ๐ŸŽฏ
  • "Find implementations" finds implementations recursively
  • "Go to definition" on a forwarding wrapper like make_unique also offers the called constructor as a target โ€” the way that just works now ๐ŸŒธ
  • "Document symbols" supports the new symbol tags added in LSP 3.18
  • "Workspace symbols" makes sure that a primary definition is ranked above typedefs ๐Ÿ‘‘

๐Ÿช„ Inlay hints

  • Correct inlay hints are shown for dependent calls to functions with an explicit object parameter (that's C++23 "deducing this", iykyk) โœจ

๐Ÿ› ๏ธ Code actions

  • A new tweak, "Create function body out-of-line", was added โ€” it creates an implementation for a function declaration ๐Ÿซถ
  • The "define out of line" code action has improved logic for finding an insertion point, and it considers =default functions now.
  • The "define out of line" code action also has improved logic for using qualified names in the printed definition ๐Ÿ’…

๐ŸŒˆ Semantic Highlighting

  • Fixed a regression in the highlighting of using-declarations and the symbols they introduce. We are so back.

๐Ÿ“ž Call hierarchy

  • Removed the redundant symbol name from hierarchy item details ๐Ÿงน

๐ŸŒ Remote index

  • Improved cross platform support โ€” like, serving an index to Windows clients from a server running on Linux. fr fr, it just works now.

๐Ÿงš Improvements to C++20 Modules support ๐Ÿงš

  • Enabled content validation for module input files โœ…
  • Fixed handling of relative paths in prebuilt module files
  • Clangd's preamble optimization is skipped for translation units that use modules. (The two features have buggy interactions, and modules independently provide much of the same performance benefit โ€” so it's fine, bestie ๐Ÿ’œ)
  • Support different modules with the same name in a project ๐Ÿ‘ฏ
  • Cache built module files across clangd invocation ๐Ÿ’พ
  • Module builds respect compile command edits specified via CompileFlags in clangd configuration
  • Invalidate the preamble when new module imports are added

๐ŸŒธ Miscellaneous

  • The "switch between source/header" command prefers an .hpp file over an .h file if both are present ๐Ÿฅ‡
  • Added a new standalone tool clangd-remap for remapping paths inside clangd background index files ๐Ÿ› ๏ธ
  • Various crash fixes and stability improvements (the classic "trust me, it's better now" bullet ๐Ÿ’€)

๐ŸŽ€ Improvements to clang-doc ๐ŸŽ€

(that's it. that's the section. the glow-up was the removals up top, bestie ๐Ÿซถ)

๐Ÿ” Improvements to clang-query ๐Ÿ”

(same energy โ€” nothing listed here, carry on ๐Ÿ’…)


๐Ÿ’… Improvements to clang-tidy ๐Ÿ’…

  • The check_clang_tidy.py script got a glow-up: a new -check-header argument that simplifies testing of header files โœจ This argument automatically manages the creation of temporary header files and makes sure diagnostics and fixes are verified for the specified headers. So convenient.
  • clang-tidy -store-check-profile now generates valid JSON even when the source file path contains characters that need JSON escaping ๐ŸŽฏ
  • clang-tidy now preserves literal backslash characters in POSIX source paths ๐Ÿซถ
  • clang-tidy and the clang compiler now use the same logic for suppressing compiler diagnostics in system headers and in expansions of macros defined in system headers. Before this, tidy's default setting overzealously suppressed some diagnostics that the compiler would have emitted. (Like โ€” tidy suppressed many clang-diagnostic-invalid-offsetof reports because they usually occur in an expansion of the macro offsetof. Rude. Not anymore. ๐Ÿ’€)
  • clang-tidy will no longer exit immediately if the only enabled checks are clang-diagnostic-* ones. That means you can use clang-tidy purely as a frontend to Clang's builtin warnings ๐Ÿ‘‘
  • On Windows, clang-tidy now better matches mixes of forwards and backwards slashes when using the --line-filter option. This also improves piping from git to clang-tidy-diff.py, where slashes now get automatically normalized. No more slash drama ๐Ÿฉท

๐ŸŒŸ New checks ๐ŸŒŸ

  • New bugprone-assignment-in-selection-statement check. Finds assignments within selection statements ๐Ÿ‘€
  • New bugprone-missing-end-comparison check. Finds instances where the result of a standard algorithm is used in a Boolean context without being compared to the end iterator.
  • New bugprone-unsafe-to-allow-exceptions check. Finds functions where throwing exceptions is unsafe but the function is still marked as potentially throwing ๐Ÿ’€
  • New llvm-formatv-string check. Validates llvm::formatv format strings against the provided arguments, diagnosing mismatched argument counts, unused arguments, and mixed index styles โœจ
  • New llvm-redundant-casting check. Points out uses of cast<>, dyn_cast<> and their or_null variants that are unnecessary because the argument already is of the target type, or a derived type thereof. It also does the same kind of analysis for calls to isa<> that always return true ๐Ÿ™ˆ
  • New llvm-type-switch-case-types check. Finds llvm::TypeSwitch::Case calls with redundant explicit template arguments that can be inferred from the lambda parameter type.
  • New llvm-use-vector-utils check. Finds calls to llvm::to_vector(llvm::map_range(...)) and llvm::to_vector(llvm::make_filter_range(...)) that can be swapped out for llvm::map_to_vector and llvm::filter_to_vector ๐ŸŽ€
  • New misc-static-initialization-cycle check. Finds cyclical initialization of static variables ๐Ÿ”„
  • New modernize-use-std-bit check. Finds common idioms that can be replaced by standard functions from the <bit> C++20 header โญ
  • New modernize-use-string-view check. Looks for functions returning std::[w|u8|u16|u32]string and suggests changing it to std::[...]string_view for performance reasons, if possible ๐Ÿš€
  • New modernize-use-structured-binding check. Finds places where structured bindings could be used to decompose pairs, and suggests replacing them ๐Ÿซถ
  • New performance-string-view-conversions check.

Finds and removes those redundant glow-up conversions from std::[w|u8|u16|u32]string_view to std::[...]string when you're inside a call expression that's expecting a std::[...]string_view anyway โ€” like bestie, why are we round-tripping, the vibe was already correct ๐Ÿ’…

  • New performance-use-std-move check. Suggests slotting in a std::move(...) so your copy assignment operator calls get to be move assignment ones instead โ€” but only when it's actually valid and worth it, no cap. โœจ
  • New readability-redundant-lambda-parameter-list check. Catches lambda expressions rocking a redundant empty parameter list and justโ€ฆ removes it. Clean. Iconic. ๐ŸŽ€
  • New readability-redundant-nested-if check. Spots nested if statements that could lowkey be one single if if you just combined the conditions with &&. Less nesting = more main character energy ๐Ÿฆ‹
  • New readability-redundant-qualified-alias check. Finds redundant identity type aliases that just re-expose a qualified name โ€” those can be swapped out for a using declaration instead. Slay ๐ŸŒธ
  • New readability-trailing-comma check. Checks whether trailing commas are present (or absent!) in enum definitions and initializer lists. Yes it's a whole check about commas. Yes it's obsessed with consistency. ๐Ÿ’…

โœจ New check aliases โœจ

  • Renamed cert-exp45-c to bugprone-assignment-in-selection-statement. New name, same energy ๐Ÿ”ฅ
  • Renamed cppcoreguidelines-explicit-constructor to misc-explicit-constructor. The cppcoreguidelines-explicit-constructor name is kept as an alias so nobody's build breaks, we're not monsters ๐Ÿซถ
  • Renamed google-explicit-constructor to misc-explicit-constructor. Yep, the google-explicit-constructor name is kept as an alias too ๐Ÿ˜ญ
  • Renamed performance-faster-string-find to performance-prefer-single-char-overloads. And yup โ€” the performance-faster-string-find name is kept as an alias, breathe ๐Ÿ’–

๐Ÿ› ๏ธ Changes in existing checks ๐Ÿ› ๏ธ

Okay so this is the long part, grab a snack ๐Ÿ“

  • Improved altera-id-dependent-backward-branch by fixing false positives when regular variable or field assignments show up in loop conditions โ€” plus note locations for inferred ID-dependent fields. Less fake-drama reporting ๐Ÿ’€
  • Improved altera-unroll-loops by fixing a crash when it's analyzing a for loop whose initialization statement declares more than one variable. Multiple declarations in one init? Handled now โœจ
  • Improved bugprone-argument-comment:
    • Now checks for C++11 inherited constructors.
    • Adds the CommentAnonymousInitLists, CommentTypedInitLists, and CommentParenthesizedTemporaries options so it can comment braced-init list arguments and explicit temporary constructions โ€” think {}, Type{}, and Type() ๐ŸŽ€
  • Improved bugprone-bad-signal-to-kill-thread by fixing false negatives when the SIGTERM macro comes from a precompiled header. Sneaky little macro, we see you now ๐Ÿ‘€
  • Improved bugprone-branch-clone by fixing a false positive where if/else branches containing inline assembly got reported as identical even though they differed only in the asm string or clobber list. They were NOT the same, bestie, and now the check knows ๐Ÿ™ˆ
  • Improved bugprone-casting-through-void by running only on C++ files, because the suggested reinterpret_cast literally doesn't exist in pure C. Can't recommend what isn't there ๐Ÿ’…
  • Improved bugprone-derived-method-shadowing-base-method by correctly ignoring function templates. Not everything is a shadow, sometimes it's just a template โญ
  • Improved bugprone-exception-escape by adding the TreatFunctionsWithoutSpecificationAsThrowing option โ€” it supports reporting for unannotated functions, lets you get reports when no explicit throw is seen, and lets you tune known and unknown implementations separately. Chef's kiss of configurability ๐Ÿ”ฅ
  • Improved bugprone-fold-init-type by detecting precision loss in overloads that use transparent standard functors (e.g. std::plus<>) for std::accumulate, std::reduce, and std::inner_product. Precision loss found, politely dragged ๐Ÿฆ‹
  • Improved bugprone-inc-dec-in-conditions by fixing a false positive when increment/decrement operators appear inside lambda bodies that happen to be part of a condition expression. Lambdas get to live their lives ๐Ÿ’œ
  • Improved bugprone-incorrect-enable-if so it doesn't insert an extraneous typename on code like typename std::enable_if<...> โ€” where there's already a typename and literally only the ::type at the end is missing. No double-typename moment ๐Ÿ˜ญ
  • Improved bugprone-macro-parentheses by printing the macro definition in the warning message if the macro is defined on the command line. Receipts included ๐Ÿงพ
  • Improved bugprone-misplaced-widening-cast by fixing a false positive on bit field assignments when the CheckImplicitCasts option is enabled. The check now uses the actual bit field width instead of the declared type to decide whether widening happens โ€” accurate or nothing ๐Ÿ’…
  • Improved bugprone-move-forwarding-reference by fixing some false positives in the context of moved lambda captures. Lambda captures are complicated, we're being nice about it ๐Ÿซถ
  • Improved bugprone-narrowing-conversions by fixing a false positive when converting a bool to a signed integer type. That one's fine actually โœจ
  • Improved bugprone-pointer-arithmetic-on-polymorphic-object by fixing a false positive when operator[] is used in a dependent context ๐Ÿ“š
  • Improved bugprone-random-generator-seed by adding a new note at the location of the field when the generator is implicitly initialized with a default seed value. Now you get pointed at the actual culprit ๐ŸŽฏ
  • Improved bugprone-std-namespace-modification by fixing false positives when you extend the standard library with a specialization of a user-defined type, and by removing detection of compiler-generated std namespace extensions. Less collateral damage ๐Ÿ’€
  • Improved bugprone-string-constructor to detect suspicious string constructor calls when the string class constructor has a default allocator argument ๐Ÿ‘‘
  • Improved bugprone-throwing-static-initialization by adding the AllowedTypes option โ€” with it you can exclude static declarations of specific types from the check. Your faves are safe ๐ŸŒˆ
  • Improved bugprone-unchecked-optional-access so it recognizes common GoogleTest macros like ASSERT_TRUE and ASSERT_FALSE, which cuts down the false positives in test code big time ๐Ÿงช
  • Improved bugprone-unsafe-functions by adding std::get_temporary_buffer to the default list of unsafe functions. Reminder: this function is unsafe, useless, deprecated in C++17 and removed in C++20. It's giving historical artifact ๐Ÿ’€
  • Improved bugprone-use-after-move:
    • Now includes the name of the invalidating function in the warning message when a custom invalidation function is used (via the InvalidationFunctions option). Name and shame, helpfully ๐Ÿ’…
    • Adds support for annotating user-defined types as having the same moved-from semantics as standard smart pointers.
    • No longer reports an explicit call to the destructor after a move as an invalid use. Destructor said what it said ๐Ÿซถ
    • Avoids false positives when moving an object to a base type and then accessing non-base members.
    • Avoids false positives when a moved object is reinitialized via the base class's operator=.
    • Fixes a false positive when a moved-from variable is reinitialized via a std::tie() assignment (e.g. std::tie(a, b) = f(std::move(a), std::move(b))). The tuple assignment writes back through the stored references, which fully reinitializes the captured variables. Genuinely a full reset, bestie โœจ
    • Avoids false positives when forwarding a derived object to initialize a base class subobject.
  • Improved cert-err33-c by not inheriting the CheckedReturnTypes option from bugprone-unused-return-value โ€” that inheritance was causing false positives on functions returning std::error_code or similar types ๐Ÿ˜ค
  • Improved cppcoreguidelines-avoid-capturing-lambda-coroutines by adding the AllowExplicitObjectParameters option. When it's enabled, lambda coroutines using C++23 deducing this (explicit object parameter) don't get flagged ๐Ÿ‘‘
  • Improved cppcoreguidelines-avoid-non-const-global-variables by adding the IgnoreMacros option. When enabled, non-const global variables defined in macros are ignored. Macro girlies stay unbothered ๐Ÿ’…
  • Improved cppcoreguidelines-init-variables by making sure member pointers are correctly flagged as uninitialized ๐ŸŽฏ
  • Fixed cppcoreguidelines-init-variables by excluding the Objective-C for-in loop variable declaration. Different dialect, different rules ๐ŸŒธ
  • Improved cppcoreguidelines-missing-std-forward by:
    • Correctly handling forwarding in deeply nested lambdas. Yes, even those lambdas ๐Ÿช†
    • Fixing a false negative when multiple parameters are used in a lambda and only some of them are forwarded.
    • Fixing a false positive for constrained template parameters.
    • Fixing a false positive with std::forward in brace-init and paren-init lambda captures such as [t{std::forward<T>(t)}]. Look at that syntax, she's doing great ๐Ÿ”ฅ
  • Improved cppcoreguidelines-pro-type-member-init by fixing a false positive when a base class has a forward declaration before its definition ๐Ÿฆ‹
  • Improved cppcoreguidelines-pro-type-vararg so it no longer warns on builtins with custom type checking (e.g. type-generic builtins like __builtin_clzg) that use variadic declarations purely as an implementation detail. Not every ... is a vararg crime ๐Ÿ’€
  • Improved cppcoreguidelines-rvalue-reference-param-not-moved:
    • Fixed a false positive on implicitly generated functions such as inherited constructors.
    • Added the AllowImplicitMove option so it doesn't warn when an rvalue reference parameter is returned without an explicit std::move. Sometimes the move is implied, bestie ๐Ÿซถ
  • Improved cppcoreguidelines-special-member-functions by fixing a false positive with the AllowImplicitlyDeletedCopyOrMove option for classes whose copy operations are implicitly deleted by a non-copyable base class ๐Ÿ’œ
  • Improved cppcoreguidelines-use-enum-class by adding the IgnoreMacros option. When enabled, unscoped enum declarations inside macros are ignored โœจ
  • Improved fuchsia-statically-constructed-objects by fixing a crash when checking value-dependent static member initializers in class templates. No more crashing, only thriving ๐ŸŒˆ
  • Improved llvm-use-ranges by adding support for the following algorithms: std::accumulate, std::replace_copy, and std::replace_copy_if. The range-ification continues ๐Ÿ‘‘
  • Improved misc-const-correctness:
    • Added support for analyzing function parameters with the AnalyzeParameters option.
    • Fixed a false positive where an array of pointers to const was incorrectly diagnosed as allowing the pointee to be made const.
    • Fixed a false positive where a pointer used with placement new was incorrectly diagnosed as allowing the pointee to be made const.
    • Enabled the check for variables declared with auto (configurable via the AnalyzeAutoVariables option) and for lambdas (configurable via the AnalyzeLambdas option). More coverage, more const ๐ŸŽ€
    • Fixed a false positive where calling a non-const member function on a pointer was incorrectly treated as mutating the pointer itself, when it only mutates the pointee. Rude assumption, honestly ๐Ÿ˜ค
    • Fixed false positives when pointers were later passed or bound through const-qualified pointer references.
    • Fixed a false positive where a pointer returned as a non-const void * was incorrectly diagnosed as allowing its pointee to be made const.
  • Improved misc-multiple-inheritance by avoiding false positives when virtual inheritance causes concrete bases to be counted more than once. One base, one count, periodt ๐Ÿ’…
  • Improved misc-redundant-expression:
    • Fixed a crash when evaluating bitwise comparisons against integer constants wider than 64 bits.
  • Avoided false positives when comparing expressions that are structurally identical but use different type aliases. iykyk, aliases are just nicknames and we're not out here accusing nicknames of being different people ๐Ÿ’…
  • Improved the misc-throw-by-value-catch-by-reference check:
    • Fixed the WarnOnLargeObject option so it uses the correct name when storing the configuration. name slip-ups? not on our watch โœจ
    • Fixed the CheckThrowTemporaries option to correctly reflect its configured value in exported settings.
  • Improved the misc-unused-parameters check by adding the IgnoreMacroParameters option, so it can suppress warnings for unused parameters whose declarations originate from macro expansions ๐ŸŽ€
  • Improved misc-unused-using-decls so it doesn't diagnose using declarations as unused if they're exported from a module ๐Ÿ‘‘
  • Improved misc-use-internal-linkage so it doesn't suggest giving internal linkage to entities defined in C++ module interface units. Because it only sees one file at a time, the check can't be sure such entities aren't referenced in any other files of that module. lowkey humble about its limits, love that ๐Ÿซถ
  • Improved the modernize-avoid-c-style-cast check by fixing an invalid fix-it generated when replacing casts that directly follow a keyword or identifier.
  • Improved the modernize-deprecated-headers check by avoiding false positives on project headers that use the same name as a standard library header.
  • Improved modernize-loop-convert checks to insert a space when replacing *it with the loop variable in expressions like delete*it, where the missing space would cause the keyword and the new variable to merge into a single identifier. literally two words becoming one, we don't do that here ๐Ÿ’€
  • Improved the modernize-macro-to-enum check by preserving source file line endings in fix-it replacements.
  • Improved the modernize-pass-by-value check by adding the IgnoreMacros option to suppress warnings in macros.
  • Improved the modernize-redundant-void-arg check to work in C23.
  • Improved the modernize-return-braced-init-list check to apply fix-it when type qualifiers and/or reference modifiers are used with parameters.
  • Improved the modernize-type-traits check to suggest usage of std::remove_cvref_t when applicable. bestie that one's a whole vibe โœจ
  • Improved the modernize-use-default-member-init check by fixing a false positive when a constructor initializer refers to a declaration that wouldn't be visible from the inserted default member initializer. The IgnoreNonVisibleReferences option can be set to false to warn without emitting fix-its for these cases.
  • Improved the modernize-use-equals-delete check by only warning on private deleted functions if they don't have a public overload or are a special member function.
  • Improved the modernize-use-nodiscard check by avoiding false positives on functions returning specializations of class templates marked [[nodiscard]].
  • Improved the modernize-use-override check by adding the AllowVirtualAndOverride option to allow keeping virtual on methods that are also marked override.
  • Improved the modernize-use-ranges check:
    • Preserved used iterator results when replacing std::unique calls with std::ranges::unique.
    • Preserved used iterator results when replacing std::remove, std::remove_if, std::partition, std::stable_partition, and std::rotate calls with their std::ranges counterparts. keeping the iterator you actually used? slay ๐Ÿ”ฅ
  • Improved the modernize-use-scoped-lock check by fixing a false negative where multiple std::scoped_lock declarations, or a mix of std::scoped_lock and std::lock_guard declarations, in the same scope were not flagged.
  • Improved the modernize-use-std-format check:
    • Fixed a crash when an argument is part of a macro expansion.
    • Added missing #include insertion when the format function call appears as an argument to a macro.
  • Improved the modernize-use-std-print check by adding missing #include insertion when the format function call appears as an argument to a macro.
  • Improved the modernize-use-trailing-return-type check by fixing spurious missing '(' after '__has_feature' errors caused by builtin macros appearing in the return type of a function.
  • Improved the modernize-use-using check:
    • Avoid generating invalid code for function types with redundant parentheses.
    • Preserve inline comment blocks that appear between the typedef's parts. yes bestie we're keeping your comments, they're part of the aesthetic ๐ŸŽ€
    • The IgnoreExternC option is now set to true by default. heads up, the default changed! ๐Ÿ™ˆ
  • Improved the performance-enum-size check:
    • Exclude enum in extern "C" blocks.
    • Improved the ignore list to correctly handle typedef and enum.
  • Improved the performance-inefficient-string-concatenation check by adding support for detecting inefficient string concatenation in do-while loops.
  • Improved the performance-inefficient-vector-operation check by correctly handling vector-like classes when push_back/emplace_back are inherited.
  • Improved the performance-move-const-arg check by avoiding false positives on trivially copyable types with a non-public copy constructor.
  • Improved the performance-prefer-single-char-overloads check:
    • Now analyzes calls to the starts_with, ends_with, contains, and operator+= string member functions.
    • Fixes false negatives when using std::set from libstdc++.
  • Improved the performance-trivially-destructible check by fixing false positives when a class is seen through both a header include and a C++20 module import.
  • Improved the readability-braces-around-statements check by fixing a crash when diagnosing a statement that ends in the middle of a macro body expansion.
  • Improved the readability-container-size-empty check:
    • Fix a crash when a member expression has a non-identifier name.
    • Reduce verbosity by removing the note indicating source location of the empty function.
    • Fixed a false positive with suggesting empty when comparing a container to a default-constructed object of an unrelated type.
    • Extended to warn when the non-member std::size() function is used in a Boolean context or compared to 0 or 1, consistent with the existing size()/length() member call detection. periodt ๐Ÿซถ
  • Improved the readability-convert-member-functions-to-static check:
    • Fixing a false positive where const member functions were incorrectly flagged when they are part of a const/non-const overload pair.
    • Correctly detecting this usage when a generic lambda calls an overloaded member function.
  • Improved the readability-delete-null-pointer check by avoiding invalid fix-its for if statements with condition variables or initializers.
  • Improved the readability-else-after-return check:
    • Fixed missed diagnostics when if statements appear in unbraced switch case labels.
    • Fixed a false positive involving if statements which contain a return, break, etc., jumped over by a goto.
    • Fixed the check potentially breaking code by deleting one too many characters following an else or a curly brace. ๐Ÿ˜ญ deleting one extra char is giving chaos, glad that's fixed
    • Added support for handling attributed if then-branches such as [[likely]] and [[unlikely]].
    • Diagnose and remove redundant else branches after calls to [[noreturn]] functions.
  • Improved the readability-enum-initial-value check: the warning message now uses separate note diagnostics for each uninitialized enumerator, making it easier to see which specific enumerators need explicit initialization. so considerate ๐Ÿ’–
  • Improved the readability-function-size check by adding an IgnoreMacros option to exclude statements, branches, nesting levels, and variable declarations inside macros from the reported metrics.
  • Improved the readability-identifier-length check:
    • A new option, named LineCountThreshold, is added to silence warnings for short-lived variables, based on distance between declaration and last use.
    • Support for structured bindings is added. Two new options, named MinimumBindingNameLength and IgnoredBindingNames respectively, are added to configure the behavior of the check regarding this new identifier kind. By default, names with at least 2 characters are required and the only exception allowed is _. ngl very fair ๐ŸŒธ
  • Improved the readability-identifier-naming check:
    • Fixed incorrect naming style application to C++17 structured bindings.
    • Fixed a false positive where function templates could be diagnosed as generic identifiers when DefaultCase was enabled.
    • Fixed a crash in dependent base lookup when the AggressiveDependentMemberLookup option is enabled.
    • Fixed a false positive from naming style lookup for declarations inside macro arguments.
  • Improved the readability-implicit-bool-conversion check:
    • Fixed a false positive where the AllowPointerConditions and AllowIntegerConditions options did not suppress warnings when the condition expression involved temporaries (e.g. passing a string literal to a const std::string& parameter).
    • Warn and provide fix-its when a macro defined in a system header (e.g. NULL) is implicitly converted to bool. yes bestie, even NULL gets the vibe check ๐Ÿ’€
    • Added the AllowLogicalOperatorConversion option to suppress warnings on implicit conversions of logical operator results (&&, ||, !) to bool in C.
    • Fixed a false positive where bool conditions in C conditional operators were diagnosed as implicit conversions to int.
  • Improved the readability-inconsistent-ifelse-braces check to correctly handle labeled statements of if/else bodies.
  • Improved the readability-non-const-parameter check:
    • Avoid false positives on parameters used in dependent expressions (e.g. inside generic lambdas), including constructor-style dependent initializers.
    • Fixed a false positive in array subscript expressions where the types are not yet resolved.
    • Fixed a false positive when adding const to a pointer parameter would conflict with an existing overload.
    • Fixed a crash when analyzing a redeclaration whose initializer is attached to another declaration.
  • Improved the readability-redundant-casting check by adding the IgnoreImplicitCasts option (default false) to flag casts as redundant when at least one operand of a binary operation matches the cast type due to implicit conversion. For example, static_cast<float>(1.0f + 1) is now identified as redundant since 1 is implicitly converted to float. Setting this option to true restores the previous behavior. read that twice bestie, true = old behavior ๐Ÿ‘€
  • Improved the readability-redundant-member-init check by adding an IgnoreMacros option to suppress warnings when the initializer involves macros that may expand differently in other configurations.
  • Improved the readability-redundant-parentheses check:
    • Fixed a false positive for parentheses present around an overloaded operator in the context of a binary operation.
  • ๐Ÿ› Fixed a bug where clients that apply fix-its without clang-tidy's cleanup could produce invalid code by joining adjacent tokens. like bestie that's a whole mess avoided fr.
  • โœจ Added support for parentheses around subscript operators ((E1[E2]) -> E1[E2]).
  • ๐Ÿ”ง Improved readability-redundant-preprocessor check by fixing a false positive for nested #if directives using different builtin expressions such as __has_builtin and __has_cpp_attribute.
  • ๐Ÿ”ง Improved readability-simplify-boolean-expr check to provide valid fix suggestions for C23 and later by not using static_cast.
  • ๐Ÿ”ง Improved readability-simplify-subscript-expr check by fixing missing warnings when subscripting an object held inside a generic container (e.g. subscripting a std::string held inside a std::vector<std::string>). sneaky little guy, glad we caught it ๐Ÿ’…
  • ๐Ÿ”ง Improved readability-suspicious-call-argument check by avoiding a crash from invalid Abbreviations option.
  • ๐Ÿ”ง Improved readability-trivial-switch check by adding an IgnoreMacros option. When enabled, the check will ignore switch statements originating from macros. obsessed, macros get a hall pass now ๐ŸŽซ
  • ๐Ÿ”ง Improved readability-use-anyofallof check by emitting a diagnostic note to suggest materializing the temporary range when iterating over temporary range expressions or initializer lists, as reusing them directly could be unsafe. โš ๏ธ important!! that one's a real safety vibe check.

๐Ÿ—‘๏ธ Removed checks ๐Ÿ—‘

(that's it. that's the section. nothing else here, bestie โ€” an empty little room ๐Ÿซ™)

๐ŸŽฒ Miscellaneous ๐ŸŽฒ

(also just vibing here with no further content โœจ)

๐Ÿ“Ž Improvements to include-fixer ๐Ÿ“Ž

(nothing listed under this one either ๐ŸŽ€)

๐Ÿงท Improvements to clang-include-fixer ๐Ÿงท

  • ๐Ÿ’€ Fixed crashes when command-line argument parsing failed at unknown tool options.

๐Ÿงฉ Improvements to modularize ๐Ÿงฉ

(nada, empty, void ๐ŸŒธ)

๐Ÿ” Improvements to pp-trace ๐Ÿ”

(quiet section, nothing to report ๐Ÿฆ‹)

๐Ÿ–ฅ๏ธ Clang-tidy Visual Studio plugin ๐Ÿ–ฅ๏ธ

(also empty โ€” but iconic name tho ๐Ÿ‘‘)


๐ŸŽ€ CLANG-TIDY ๐ŸŽ€

okay bestie this is the big one. main character energy, no cap. ๐Ÿ’–

๐Ÿ’ซ Contents ๐Ÿ’ซ

  • Clang-Tidy
    • Using Clang-Tidy
      • Running Clang-Tidy on CUDA Files
    • Clang-Tidy Automation
      • Running Clang-Tidy in Parallel
      • Running Clang-Tidy on Diff
    • Suppressing Undesired Diagnostics

๐Ÿ‘€ See also:

Clang-Tidy Checks


๐Ÿงš 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. the way CTAD just makes things cleaner?? we love to see it ๐Ÿ’…

So this:

auto c1 = absl::MakeCleanup([] {});

const auto c2 = absl::MakeCleanup(std::function<void()>([] {}));

becomes this:

absl::Cleanup c1 = [] {};

const absl::Cleanup c2 = std::function<void()>([] {});

slay. ๐ŸŽ€

โฑ๏ธ abseil-duration-addition โฑ๏ธ

Checks for cases where addition should be performed in the absl::Time domain. So when you're adding two values and one is known to be an absl::Time, we can infer that the other one should be interpreted as an absl::Duration of a similar scale โ€” and we just make that inference explicit. lowkey clairvoyant of us ngl ๐Ÿ”ฎ

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, rare bug is still a bug bestie ๐Ÿ›

Examples:

// Original - Comparison in the floating point domain
double x;
absl::Duration d;
if (x < absl::ToDoubleSeconds(d)) ...

// Suggested - Compare in the absl::Duration domain instead
if (absl::Seconds(x) < d) ...

// Original - Comparison in the integer domain
int x;
absl::Duration d;
if (x < absl::ToInt64Microseconds(d)) ...

// Suggested - Compare in the absl::Duration domain instead
if (absl::Microseconds(x) < d) ...

๐ŸŽญ abseil-duration-conversion-cast ๐ŸŽญ

Checks for casts of absl::Duration conversion functions, and recommends the right conversion function instead. it's giving "you're using the wrong tool, sweetie" ๐Ÿ”ง

Examples:

// Original - Cast from a double to an integer
absl::Duration d;
int i = static_cast<int>(absl::ToDoubleSeconds(d));

// Suggested - Use the integer conversion function directly.
int i = absl::ToInt64Seconds(d);

// Original - Cast from a double to an integer
absl::Duration d;
double x = static_cast<double>(absl::ToInt64Seconds(d));

// Suggested - Use the integer conversion function directly.
double x = absl::ToDoubleSeconds(d);
Note: In the second example, the suggested fix could yield a different result, as the conversion to integer could truncate. In practice, this is very rare, and you should use absl::Trunc to perform this operation explicitly instead. โš ๏ธ don't just blindly apply the fix, okay?? read the note!! ๐Ÿ˜ญ

โž— abseil-duration-division โž—

absl::Duration arithmetic works like it does with integers. That means that division of two absl::Duration objects returns an int64 with any fractional component truncated toward 0. See this link for more information on arithmetic with absl::Duration.

For example:

absl::Duration d = absl::Seconds(3.5);
int64 sec1 = d / absl::Seconds(1);     // Truncates toward 0.
int64 sec2 = absl::ToInt64Seconds(d);  // Equivalent to division.
assert(sec1 == 3 && sec2 == 3);

double dsec = d / absl::Seconds(1);  // WRONG: Still truncates toward 0.
assert(dsec == 3.0);

If you want floating-point division, you should use either the absl::FDivDuration() function, or one of the unit conversion functions such as absl::ToDoubleSeconds(). For example:

absl::Duration d = absl::Seconds(3.5);
double dsec1 = absl::FDivDuration(d, absl::Seconds(1));  // GOOD: No truncation.
double dsec2 = absl::ToDoubleSeconds(d);                 // GOOD: No truncation.
assert(dsec1 == 3.5 && dsec2 == 3.5);

This check looks for uses of absl::Duration division that is done in a floating-point context, and recommends the use of a function that returns a floating-point value. ๐Ÿซถ

๐Ÿญ abseil-duration-factory-float ๐Ÿญ

Checks for cases where the floating-point overloads of various absl::Duration factory functions are called when the more-efficient integer versions could be used instead.

This check will not suggest fixes for literals which contain fractional floating point values or non-literals. It will suggest removing superfluous casts. ๐Ÿ’…

Examples:

// Original - Providing a floating-point literal.
absl::Duration d = absl::Seconds(10.0);

// Suggested - Use an integer instead.
absl::Duration d = absl::Seconds(10);

// Original - Explicitly casting to a floating-point type.
absl::Duration d = absl::Seconds(static_cast<double>(10));

// Suggested - Remove the explicit cast
absl::Duration d = absl::Seconds(10);

๐Ÿ“ abseil-duration-factory-scale ๐Ÿ“

Checks for cases where arguments to absl::Duration factory functions are scaled internally and could be changed to a different factory function. This check also looks for arguments with a zero value and suggests using absl::ZeroDuration() instead.

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 the absl:::Seconds is literally what's written there, don't come for me, I'm just the messenger ๐Ÿ˜ญ๐Ÿซถ)

โž– abseil-duration-subtraction โž–

Checks for cases where subtraction should be performed in the absl::Duration domain. When subtracting two values, and the first one is known to be a conversion from absl::Duration, we can infer that the second should also be interpreted as an absl::Duration, and make that inference explicit. detective vibes fr ๐Ÿ”

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 is possible that multiple fixes may overlap (as in the case of nested expressions), so not all occurrences can be transformed in one run. In particular, this may occur for nested subtraction expressions. Running clang-tidy multiple times will find and fix these overlaps. so like... run it again bestie, it's not broken, it's just thorough ๐Ÿ’€โœจ

๐Ÿ”„ abseil-duration-unnecessary-conversion ๐Ÿ”„

Finds and fixes cases where absl::Duration values are being converted to numeric types and back again. the round trip to nowhere, we're cancelling it ๐Ÿšซ

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 an absl::Duration might be a truncating operation if the value is not aligned to the scale of conversion. In the rare case where this is the intended result, callers should use absl::Trunc to truncate explicitly. โš ๏ธ rare but real, okay?? don't @ me when your numbers shift ๐Ÿ˜ค

โœ‚๏ธ abseil-faster-strsplit-delimiter โœ‚๏ธ

Finds instances of absl::StrSplit() or absl::MaxSplits() where the delimiter is a single character string literal and replaces with a character. The check will offer a suggestion to change the string literal into a character. It will also catch code using absl::ByAnyChar() for just a single character and will transform that into a single character as well. efficiency queen behavior ๐Ÿ‘‘๐Ÿ”ฅ

So like, these changes will give you the exact same result, but using characters rather than single character string literals is more efficient and more readable โ€” it's giving best of both worlds, no cap โœจ

Examples, bestie:

// Original - the argument is a string literal.
for (auto piece : absl::StrSplit(str, "B")) {

// Suggested - the argument is a character, which causes the more efficient
// overload of absl::StrSplit() to be used.
for (auto piece : absl::StrSplit(str, 'B')) {

// Original - the argument is a string literal inside absl::ByAnyChar call.
for (auto piece : absl::StrSplit(str, absl::ByAnyChar("B"))) {

// Suggested - the argument is a character, which causes the more efficient
// overload of absl::StrSplit() to be used and we do not need absl::ByAnyChar
// anymore.
for (auto piece : absl::StrSplit(str, 'B')) {

// Original - the argument is a string literal inside absl::MaxSplits call.
for (auto piece : absl::StrSplit(str, absl::MaxSplits("B", 1))) {

// Suggested - the argument is a character, which causes the more efficient
// overload of absl::StrSplit() to be used.
for (auto piece : absl::StrSplit(str, absl::MaxSplits('B', 1))) {

๐Ÿ’€ abseil-no-internal-dependencies

Warns you if your code using Abseil depends on internal details. If something lives in a namespace that includes the word "internal", your code is not allowed to depend on it, because it's literally just 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 โ€” and nobody wants that.

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. Like fr, code should not open namespace absl โ€” that conflicts with the guidelines and may result in breakage ๐Ÿ’…

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 removal of unnecessary calls to absl::StrCat when the result is being passed to another call to absl::StrCat or absl::StrAppend.

The extra calls cause unnecessary temporary strings to be constructed โ€” wasteful, honestly ๐Ÿ˜ญ. Removing them makes the code smaller and faster.

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 you use absl::StrAppend() instead.

The extra calls cause unnecessary temporary strings to be constructed. Removing them makes the code smaller and faster โ€” periodt โœจ

a = absl::StrCat(a, b); // Use absl::StrAppend(&a, b) instead.
Does not diagnose cases where absl::StrCat() is used as a template argument for a functor. Just so you know ๐Ÿ‘€

๐Ÿ”Ž abseil-string-find-startswith

Checks whether a std::string::find() or std::string::rfind() (and corresponding std::string_view methods) result is compared with 0, and suggests replacing with absl::StartsWith(). This is both a readability and a performance issue, iconic honestly ๐Ÿ‘‘

starts_with was added as a built-in function on those types in C++20. If available, prefer enabling modernize-use-starts-ends-with instead of this check. iykyk.

string s = "...";
if (s.find("Hello World") == 0) { /* do something */ }
if (s.rfind("Hello World", 0) == 0) { /* do something */ }

becomes

string s = "...";
if (absl::StartsWith(s, "Hello World")) { /* do something */ }
if (absl::StartsWith(s, "Hello World")) { /* do something */ }

Options

  • StringLikeClasses โ€” Semicolon-separated list of names of string-like classes. By default both std::basic_string and std::basic_string_view are considered. The list of methods to be considered is fixed.
  • IncludeStyle โ€” A string specifying which include-style is used, llvm or google. Default is llvm.
  • AbseilStringsMatchHeader โ€” The location of Abseil's strings/match.h. Defaults to absl/strings/match.h.

๐Ÿ•ต๏ธ abseil-string-find-str-contains

Finds s.find(...) == string::npos comparisons (for various string-like types) and suggests replacing with absl::StrContains().

This improves readability and reduces the likelihood of accidentally mixing find() and npos from different string-like types โ€” which is the kind of bug that has you staring at your screen like ๐Ÿ˜ญ at 2am.

By default, "string-like types" includes ::std::basic_string, ::std::basic_string_view, and ::absl::string_view. See the StringLikeClasses option to change this.

std::string s = "...";
if (s.find("Hello World") == std::string::npos) { /* do something */ }

absl::string_view a = "...";
if (absl::string_view::npos != a.find("Hello World")) { /* do something */ }

becomes

std::string s = "...";
if (!absl::StrContains(s, "Hello World")) { /* do something */ }

absl::string_view a = "...";
if (absl::StrContains(a, "Hello World")) { /* do something */ }

Options

  • StringLikeClasses โ€” Semicolon-separated list of names of string-like classes. By default includes ::std::basic_string, ::std::basic_string_view, and ::absl::string_view.
  • IncludeStyle โ€” A string specifying which include-style is used, llvm or google. Default is llvm.
  • AbseilStringsMatchHeader โ€” The location of Abseil's strings/match.h. Defaults to absl/strings/match.h.

โฐ abseil-time-comparison

Prefer comparisons in the absl::Time domain instead of the integer domain. Time domain stays in its lane, so to speak ๐Ÿ’…

N.B.: In cases where an absl::Time is being converted to an integer, alignment may occur. If the comparison depends on this alignment, doing the comparison in the absl::Time domain may yield a different result. In practice this is very rare, and still indicates a bug which should be fixed. So like... still 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

Finds and fixes absl::Time subtraction expressions to do subtraction in the Time domain instead of the numeric domain.

There are two cases of Time subtraction in which we need to deduce additional type information:

  • When the result is an absl::Duration and the first argument is an absl::Time.
  • When the second argument is a absl::Time.

In the first case, we must know the result of the operation, since without that the second operand could be either an absl::Time or an absl::Duration. In the second case, the first operand must be an absl::Time, because subtracting an absl::Time from an absl::Duration is not defined.

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-unchecked-statusor-access

This check identifies unsafe accesses to values contained in absl::StatusOr<T> objects. Below we will refer to this type as StatusOr<T>.

An access to the value of an StatusOr<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 vs. undefined behavior ๐Ÿ‘€

An access to the value of a StatusOr<T> is considered safe if and only if code in the local scope (e.g. function body) ensures that the status of the StatusOr<T> is ok in all possible execution paths that can reach the access. That should happen either through an explicit check, using the StatusOr<T>::ok member function, or by constructing the StatusOr<T> in a way that shows that its status is unambiguously ok (e.g. by passing a value to its constructor).

Below we list some examples of safe and unsafe StatusOr<T> access patterns.

Note: If the check isn't behaving as you would have expected on a code snippet, please report it. Besties help besties ๐Ÿซถ

False negatives

This check generally does not generate false negatives. That means that if an access is not marked as unsafe, it is provably safe. If it cannot prove an access safe, it is assumed to be unsafe. In some cases, the static analysis cannot prove an access safe even though it is, for a variety of reasons (e.g. unmodelled invariants of functions called). In these cases, the analysis does produce false positive reports.

That being said, there are some heuristics used that in very rare cases might be incorrect:

  • a const method accessor (without arguments) that returns different values when called multiple times.

If you think the check generated a false negative, please report it.

Known limitations

This is a non-exhaustive list of constructs that are currently not modelled in the check and will lead to false positives:

  • Checking a StatusOr and then capturing it in a lambda
  • Indexing into a container with the same index
  • Project specific helper-functions,
  • Functions with a stable return value
  • Any cross-function reasoning. This is by design and will not change in the future.

Checking if the status is ok, then accessing the value

The check recognizes all straightforward ways for checking the status and accessing the value contained in a StatusOr<T> object. For example:

void f(absl::StatusOr<int> x) {
  if (x.ok()) {
    use(*x);
  }
}

Checking if the status is ok, then accessing the value from a copy

The criteria that the check uses is semantic, not syntactic. It recognizes when a copy of the StatusOr<T> object being accessed is known to have ok status. For example:

void f(absl::StatusOr<int> x1) {
  if (x1.ok()) {
    absl::optional<int> x2 = x1;
    use(*x2);
  }
}

Ensuring that the status is ok using common macros ๐ŸŽ€

The check knows about common macros like ABSL_CHECK or ABSL_CHECK_OK. Those can be used to ensure that the status of a StatusOr<T> object is ok. For example:

void f(absl::StatusOr<int> x) {
  ABSL_CHECK_OK(x);
  use(*x);
}

Ensuring that the status is ok using googletest macros ๐Ÿงช

The check is aware of googletest (or gtest) macros and matchers. Accessing the value of a StatusOr<T> object is considered safe if it's preceded by an ASSERT_ macro that ensures the status is ok. For example:

TEST(MySuite, MyTest) {
  absl::StatusOr<int> x = foo();
  ASSERT_OK(x);
  use(*x);
}

TEST(MySuite, MyOtherTest) {
  absl::StatusOr<int> x = foo();
  ASSERT_THAT(x, absl_testing::IsOk());
  use(*x);
}

The following googletest macros are supported:

  • ASSERT_OK(...)
  • ASSERT_TRUE(...)
  • ASSERT_FALSE(...)
  • ASSERT_THAT(...)

The following matchers are supported:

  • IsOk()
  • StatusIs(...)
  • IsOkAndHolds(...)
  • CanonicalStatusIs(...)
โš ๏ธ Note, bestie: EXPECT_ macros (like EXPECT_OK or EXPECT_TRUE(x.ok())) do not make subsequent accesses safe, because they don't terminate the test execution. No cap.

Ensuring that the status is ok, then accessing the value in a correlated branch ๐ŸŒฟ

The check is aware of correlated branches in the code, and can figure out when a StatusOr<T> object is ensured to have ok status on all execution paths that lead to an access. For example:

void f(absl::StatusOr<int> x) {
  bool safe = false;
  if (x.ok() && SomeOtherCondition()) {
    safe = true;
  }
  // ... more code...
  if (safe) {
    use(*x);
  }
}

Accessing the value without checking the status ๐Ÿ˜ญ

The check flags accesses to the value that are not locally guarded by a status check:

void f1(absl::StatusOr<int> x) {
  use(*x); // unsafe: it is unclear whether the status of `x` is ok.
}

void f2(absl::StatusOr<MyStruct> x) {
  use(x->member); // unsafe: it is unclear whether the status of `x` is ok.
}

void f3(absl::StatusOr<int> x) {
  use(x.value()); // unsafe: it is unclear whether the status of `x` is ok.
}

Use ABSL_CHECK_OK to signal that you knowingly want to crash on non-OK values. Intentional chaos, we respect it.

๐Ÿ’€ NOTE: Even though using .value() on a non-ok() StatusOr is defined to crash, it's often unintentional. That's why our checker flags those as well.

Accessing the value in the wrong branch ๐Ÿšซ

The check is aware of the state of a StatusOr<T> object in different branches of the code. For example:

void f(absl::StatusOr<int> x) {
  if (x.ok()) {
  } else {
    use(*x); // unsafe: it is clear that the status of `x` is *not* ok.
  }
}

Assuming a function result to be stable ๐Ÿฆ‹

The check is aware that function results might not be stable. Like, consecutive calls to the same function might return different values. For example:

void f(Foo foo) {
  if (foo.x().ok()) {
    use(*foo.x()); // unsafe: it is unclear whether the status of `foo.x()` is ok.
  }
}

In such cases it's best to store the result of the function call in a local variable and use that to access the value. For example:

void f(Foo foo) {
  if (const auto& x = foo.x(); x.ok()) {
    use(*x);
  }
}

The check does assume that const-qualified accessor functions return a stable value if no non-const function was called between the two calls:

class Foo {
  const absl::StatusOr<int>& get() const {
    [...];
  }
}
void f(Foo foo) {
  if (foo.get().ok()) {
    use(*foo.get());
  }
}

If there is a call to a non-const-qualified function, the check assumes the return value of the accessor was mutated. Sneaky little mutation, we see you.

class Foo {
  const absl::StatusOr<int>& get() const {
    [...];
  }
  void mutate();
}
void f(Foo foo) {
  if (foo.get().ok()) {
    foo.mutate();
    use(*foo.get()); // unsafe: `mutate()` might have changed the state of the object
  }
}

Relying 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 the status of `foo.GetProperty("bar")` is ok.
  }
}

In such cases it's best to check explicitly that the status of the StatusOr<T> object is ok. For example:

void f(Foo foo) {
  if (const auto& property = foo.GetProperty("bar"); property.ok()) {
    use(*property);
  }
}

Checking if the StatusOr<T> is ok, then passing it 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 status of the StatusOr<T> is ok. For example:

void g(absl::StatusOr<int> x) {
  use(*x); // unsafe: it is unclear whether the status of `x` is ok.
}

void f(absl::StatusOr<int> x) {
  if (x.ok()) {
    g(x);
  }
}

In such cases it's best to either pass the value directly when calling a function, or check that the status of the StatusOr<T> is ok in the local scope of the callee. For example:

void g(int val) {
  use(val);
}

void f(absl::StatusOr<int> x) {
  if (x.ok()) {
    g(*x);
  }
}

Aliases created via using declarations ๐Ÿท๏ธ

The check is aware of aliases of StatusOr<T> types that are created via using declarations. For example:

using StatusOrInt = absl::StatusOr<int>;

void f(StatusOrInt x) {
  use(*x); // unsafe: it is unclear whether the status of `x` is ok.
}

Containers ๐Ÿ“

The check is more strict than necessary when it comes to containers of StatusOr<T> values. Simply checking that the status of an element of a container is ok is not sufficient to deem accessing it safe. For example:

void f(std::vector<absl::StatusOr<int>> x) {
  if (x[0].ok()) {
    use(*x[0]); // unsafe: it is unclear whether the status of `x[0]` is ok.
  }
}

You need to grab a reference to a particular object and use that instead:

void f(std::vector<absl::StatusOr<int>> x) {
  absl::StatusOr<int>& x0 = x[0];
  if (x0.ok()) {
    use(*x0);
  }
}

A future version could improve the understanding of more safe usage patterns that involve containers. (manifesting ๐Ÿ™)

Lambdas ๐Ÿงš

The check is capable of reporting unsafe StatusOr<T> accesses in lambdas, but it isn't smart enough to propagate information from the surrounding context through the lambda. Which means this pattern will be reported as an unsafe access:

void f(absl::StatusOr<int> x) {
  if (x.ok()) {
    [&x]() {
      use(*x); // unsafe: it is unclear whether the status of `x` is ok.
    }
  }
}

To avoid the issue, you should instead capture the contained object, either by value or by reference. An init-capture is useful for this โ€” here we're capturing by reference:

void f(absl::StatusOr<int> x) {
  if (x.ok()) {
    [&x = *x]() {
      use(x);
    }
  }
}

Alternatively you could add a check inside the lambda where the value is accessed:

void f(absl::StatusOr<int> x) {
  [&x]() {
    if (x.ok()) {
      use(*x);
    }
  }
}

Reasoning about integers ๐Ÿ”ข

Because it uses a simple SAT solver, the check cannot reason about integer inequalities. For instance, the following will result in a false positive:

void f(int n, absl::StatusOr<int> x) {
   if (n > 0)
     CHECK_OK(x);
   if (n > 1)
     return *x;  // false positive
   return 0;
}

In fact, currently this is also the case if the two conditions are identical. SAT solver said no thoughts, head empty.


abseil-upgrade-duration-conversions โฑ๏ธ

Finds calls to absl::Duration arithmetic operators and factories whose argument needs an explicit cast to keep compiling after upcoming API changes.

The operators *=, /=, *, and / for absl::Duration currently 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 such as std::atomic<float> that would result in lossy conversion.

Additionally, the absl::Duration factory functions (absl::Hours, absl::Minutes, etc) currently accept an int64_t or a floating-point type. Similar to 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 these changes are released, passing an argument of class type will no longer compile โ€” even if the type is implicitly convertible to an arithmetic type. So go fix it now, bestie, future you says thanks.

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 user code. It's possible that this uncovers unintended behavior due to types implicitly convertible to a floating-point type. ๐Ÿ‘€


altera-id-dependent-backward-branch ๐ŸŒŠ

Finds ID-dependent variables and fields that are used within loops. This causes branches to occur inside the loops, and thus leads to performance degradation. Not the vibe.

// 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. โœจ source cited, we're so academic โœจ

๐Ÿฆ‹ altera-kernel-name-restriction

Okay so bestie, this check goes hunting for kernel files and include directives whose filename is literally kernel.cl, Verilog.cl, or VHDL.cl. And it's case insensitive, so don't think you can get slick with KERNEL.CL ๐Ÿ™ˆ

Why do we even care? Because those kernel file names make the offline compiler generate intermediate design files that have the same names as certain internal files โ€” which leads to a compilation error. It's giving "why is my build broken and why is it personal." ๐Ÿ’€

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

This one finds OpenCL kernel functions that call a barrier function but never call an ID function โ€” and the ID functions are get_local_id, get_local_id, get_group_id, or get_local_linear_id. (Yes, get_local_id is listed twice up there. That's the original's business, not mine, and I am not touching it ๐Ÿ’…)

These kernels may totally 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).

And if you're on an older version of the Altera Offline Compiler? Then these kernel functions get treated as single work-item kernels, which could be inefficient โ€” or straight up lead to errors if you actually meant NDRange semantics. ๐Ÿ˜ญ

Based on the Altera SDK for OpenCL: Best Practices Guide.

Examples, and yes I'm keeping every comment exactly as written:

// 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 to 1600 (which corresponds to version 16.00). ๐ŸŽ€

๐ŸŽ€ altera-struct-pack-align

This check finds structs that are packed or aligned inefficiently, and then recommends packing and/or aligning said structs as needed. Very much the friend who tells you your outfit is almost there. ๐Ÿซถ

Here's the tea: structs that aren't packed take up more space than they should, and accessing structs that aren't well aligned is inefficient. Two separate problems, both lowkey tragic.

Fix-its are provided to fix both of these issues by inserting and/or amending the relevant struct attributes. Slay. โœจ

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

This icon finds inner loops that have not been unrolled, plus fully unrolled loops that have unknown loop bounds or a large number of iterations. Basically it's doing a vibe check on your loops. ๐Ÿ”ฅ

Unrolling inner loops could improve the performance of OpenCL kernels โ€” that's the dream. BUT if they have unknown loop bounds or a large number of iterations, they cannot be fully unrolled, and should be partially unrolled instead. Know the difference, bestie.

Notes:

  • This check is unable to determine the number of iterations in a while or do..while loop; hence if such a loop is fully unrolled, a note is emitted advising the user to partially unroll instead.
  • In for loops, our check only works with simple arithmetic increments ( +, -, *, /). For all other increments, partial unrolling is advised.
  • Depending on the exit condition, the calculations for determining if the number of iterations is large may be off by 1. This should not be an issue since the cut-off is generally arbitrary.

Based on the Altera SDK for OpenCL: Best Practices Guide.

for (int i = 0; i < 10; i++) {  // ok: outer loops should not be unrolled
   int j = 0;
   do {  // warning: this inner do..while loop should be unrolled
      j++;
   } while (j < 15);

   int k = 0;
   #pragma unroll
   while (k < 20) {  // ok: this inner loop is already unrolled
      k++;
   }
}

int A[1000];
#pragma unroll
// warning: this loop is large and should be partially unrolled
for (int a : A) {
   printf("%d", a);
}

#pragma unroll 5
// ok: this loop is large, but is partially unrolled
for (int a : A) {
   printf("%d", a);
}

#pragma unroll
// warning: this loop is large and should be partially unrolled
for (int i = 0; i < 1000; ++i) {
   printf("%d", i);
}

#pragma unroll 5
// ok: this loop is large, but is partially unrolled
for (int i = 0; i < 1000; ++i) {
   printf("%d", i);
}

#pragma unroll
// warning: << operator not supported, recommend partial unrolling
for (int i = 0; i < 1000; i<<1) {
   printf("%d", i);
}

std::vector<int> someVector (100, 0);
int i = 0;
#pragma unroll
// note: loop may be large, recommend partial unrolling
while (i < someVector.size()) {
   someVector[i]++;
}

#pragma unroll
// note: loop may be large, recommend partial unrolling
while (true) {
   printf("In loop");
}

#pragma unroll 5
// ok: loop may be large, but is partially unrolled
while (i < someVector.size()) {
   someVector[i]++;
}

โœจ Options โœจ

  • MaxLoopIterations โ€” defines the maximum number of loop iterations that a fully unrolled loop can have. By default, it is set to 100.

In practice, this refers to the integer value of the upper bound within the loop statement's condition expression. ๐Ÿ’…


๐Ÿ“ android-cloexec-accept

Using accept()? Not recommended, bestie โ€” it's better to use accept4(). Without this flag, an opened sensitive file descriptor would remain open across a fork+exec to a lower-privileged SELinux domain. ๐Ÿšฉ

Examples:

accept(sockfd, addr, addrlen);

// becomes

accept4(sockfd, addr, addrlen, SOCK_CLOEXEC);

๐ŸŒธ android-cloexec-accept4

accept4() should include SOCK_CLOEXEC in its type argument to avoid the file descriptor leakage. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. ๐Ÿ˜ญ

Examples:

accept4(sockfd, addr, addrlen, SOCK_NONBLOCK);

// becomes

accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);

๐Ÿงš android-cloexec-creat

The usage of creat() is not recommended โ€” it's better to use open(). Periodt.

Examples:

int fd = creat(path, mode);

// becomes

int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);

๐Ÿ’… android-cloexec-dup

The usage of dup() is not recommended, it's better to use fcntl(), which can set the close-on-exec flag. Otherwise, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. ๐Ÿ‘‘

Examples:

int fd = dup(oldfd);

// becomes

int fd = fcntl(oldfd, F_DUPFD_CLOEXEC);

โญ android-cloexec-epoll-create

The usage of epoll_create() is not recommended โ€” it's better to use epoll_create1(), which allows close-on-exec. Slay. โœจ

Examples:

epoll_create(size);

// becomes

epoll_create1(EPOLL_CLOEXEC);

๐ŸŒˆ android-cloexec-epoll-create1

epoll_create1() should include EPOLL_CLOEXEC in its type argument to avoid the file descriptor leakage. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. ๐Ÿซถ

Examples:

epoll_create1(0);

// becomes

epoll_create1(EPOLL_CLOEXEC);

๐ŸŽ€ android-cloexec-fopen

fopen() should include e in their mode string; so re would be valid. This is equivalent to having set FD_CLOEXEC on that descriptor. Cute and correct. ๐Ÿ’–

Examples:

fopen("fn", "r");

// becomes

fopen("fn", "re");

๐Ÿ’œ android-cloexec-inotify-init

The usage of inotify_init() is not recommended โ€” it's better to use inotify_init1(). That's it, that's the whole vibe. ๐Ÿ’…

Examples:

inotify_init();

// becomes

inotify_init1(IN_CLOEXEC);

๐Ÿฆ‹ android-cloexec-inotify-init1

inotify_init1() should include IN_CLOEXEC in its type argument to avoid the file descriptor leakage. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain. ๐Ÿ˜ญ

Examples:

inotify_init1(IN_NONBLOCK);

// becomes

inotify_init1(IN_NONBLOCK | IN_CLOEXEC);

๐Ÿ“ android-cloexec-memfd-create

memfd_create() should include MFD_CLOEXEC in its type argument to avoid the file descriptor leakage. 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

Okay this one's the big sister of the family. A common source of security bugs is code that opens a file without using the O_CLOEXEC flag. Without that flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain, leaking that sensitive data. Not the vibe. ๐Ÿšฉ

Open-like functions โ€” including open(), openat(), and open64() โ€” should include O_CLOEXEC in their flags argument. All of them. No exceptions, bestie.

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

okay so this check is literally calling out your pipe() usage ๐Ÿ’€ like bestie, pipe() is not the vibe โ€” pipe2() is the suggested replacement, periodt. The check also adds the O_CLOEXEC flag, which marks the file descriptor to be closed in child processes. And why do we care? Because without this flag a sensitive file descriptor can be leaked to a child process โ€” potentially into a lower-privileged SELinux domain. Not the flex you think it is. ๐Ÿ™ˆ

Examples:

pipe(pipefd);

Suggested replacement:

pipe2(pipefd, O_CLOEXEC);

android-cloexec-pipe2

this one's making sure pipe2() is called with the O_CLOEXEC flag, no exceptions โœจ The check also adds the O_CLOEXEC flag that marks the file descriptor to be closed in child processes. Without this flag a sensitive file descriptor can be leaked to a child process, potentially into a lower-privileged SELinux domain. Same leak, same drama.

Examples:

pipe2(pipefd, O_NONBLOCK);

Suggested replacement:

pipe2(pipefd, O_NONBLOCK | O_CLOEXEC);

android-cloexec-socket

socket() should include SOCK_CLOEXEC in its type argument to avoid the file descriptor leakage, iykyk. Without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain โ€” and that's giving security hazard ๐Ÿšฉ

Examples:

socket(domain, type, SOCK_STREAM);

// becomes

socket(domain, type, SOCK_STREAM | SOCK_CLOEXEC);

android-comparison-in-temp-failure-retry

this check diagnoses comparisons that appear to be incorrectly placed in the argument to the TEMP_FAILURE_RETRY macro. Having such a use is incorrect in the vast majority of cases, and will often silently defeat the purpose of the TEMP_FAILURE_RETRY macro. literally sabotaging yourself ๐Ÿ˜ญ

For context (and because I will make you understand this), TEMP_FAILURE_RETRY is a convenience macro provided by both glibc and Bionic. Its purpose is to repeatedly run a syscall until it either succeeds, or fails for reasons other than being interrupted. queen behavior honestly ๐Ÿ‘‘

Example buggy usage looks like:

char cs[1];
while (TEMP_FAILURE_RETRY(read(STDIN_FILENO, cs, sizeof(cs)) != 0)) {
  // Do something with cs.
}

Because TEMP_FAILURE_RETRY will check for whether the result of the comparison is -1, and retry if so.

If you encounter this, the fix is simple: lift the comparison out of the TEMP_FAILURE_RETRY argument, like so:

char cs[1];
while (TEMP_FAILURE_RETRY(read(STDIN_FILENO, cs, sizeof(cs))) != 0) {
  // Do something with cs.
}

Options

  • RetryMacros โ€” a comma-separated list of the names of retry macros to be checked. Default is TEMP_FAILURE_RETRY.

boost-use-ranges

detects calls to standard library iterator algorithms that could be replaced with a Boost ranges version instead ๐Ÿฆ‹ glow-up time.

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 โ€” and yes, it's a long list, she's thorough ๐Ÿ’…:

std::accumulate, std::adjacent_difference, std::adjacent_find, std::all_of, std::any_of, std::binary_search, std::copy_backward, std::copy_if, std::copy, std::count_if, std::count, std::equal_range, std::equal, std::fill, std::find_end, std::find_first_of, std::find_if_not, std::find_if, std::find, std::for_each, std::generate, std::includes, std::iota, std::is_partitioned, std::is_permutation, std::is_sorted_until, std::is_sorted, std::lexicographical_compare, std::lower_bound, std::make_heap, std::max_element, std::merge, std::min_element, std::mismatch, std::next_permutation, std::none_of, std::partial_sum, std::partial_sort_copy, std::partition_copy, std::partition_point, std::partition, std::pop_heap, std::prev_permutation, std::push_heap, std::random_shuffle, std::reduce, std::remove_copy_if, std::remove_copy, std::remove_if, std::remove, std::replace_copy_if, std::replace_copy, std::replace_if, std::replace, std::reverse_copy, std::reverse, std::search, std::set_difference, std::set_intersection, std::set_symmetric_difference, std::set_union, std::sort_heap, std::sort, std::stable_partition, std::stable_sort, std::transform, std::unique_copy, std::unique, std::upper_bound.

The check will also look for the following functions from the boost::algorithm namespace:

all_of_equal, any_of_equal, any_of, apply_permutation, apply_reverse_permutation, clamp_range, copy_if_until, copy_if_while, copy_if, copy_until, copy_while, find_backward, find_if_backward, find_if_not_backward, find_if_not, find_not_backward, hex_lower, hex, iota, all_of, is_decreasing, is_increasing, is_palindrome, is_partitioned_until, is_partitioned, is_permutation, is_sorted_until, is_sorted, is_strictly_decreasing, is_strictly_increasing, none_of_equal, none_of, one_of_equal, one_of, partition_copy, partition_point, reduce, unhex.

Reverse Iteration

If calls are made using reverse iterators on containers, the code will be fixed using the boost::adaptors::reverse adaptor ๐Ÿ”„

auto AreSame = std::equal(Items1.rbegin(), Items1.rend(),
                          std::crbegin(Items2), std::crend(Items2));

Transforms to:

auto AreSame = boost::range::equal(boost::adaptors::reverse(Items1),
                                   boost::adaptors::reverse(Items2));

Options

  • IncludeStyle โ€” a string specifying which include-style is used, llvm or google. Default is llvm.
  • IncludeBoostSystem โ€” if true (default value) the boost headers are included as system headers with angle brackets (#include <boost.hpp>), otherwise quotes are used (#include "boost.hpp").
  • UseReversePipe โ€” when true (default false), fixes which involve reverse ranges will use the pipe adaptor syntax instead of the function syntax.
std::find(Items.rbegin(), Items.rend(), 0);

Transforms to:

boost::range::find(Items | boost::adaptors::reversed, 0);

boost-use-to-string

this check finds conversion from integer type like int to std::string or std::wstring using boost::lexical_cast, and replaces it with calls to std::to_string and std::to_wstring. modernize your life bestie โœจ

It doesn't replace conversion from floating points despite the to_string overloads, because it would change the behavior. we're keeping the semantics intact here, no cap.

auto str = boost::lexical_cast<std::string>(42);
auto wstr = boost::lexical_cast<std::wstring>(2137LL);

// Will be changed to
auto str = std::to_string(42);
auto wstr = std::to_wstring(2137LL);

bugprone-argument-comment

checks that argument comments match parameter names and can optionally add missing comments for literals, init-lists, and constructed temporaries ๐Ÿ‘€ she's reading your comments and she will notice.

The check understands argument comments in the form /*parameter_name=*/ that are placed right before the argument.

void f(bool foo);

...

f(/*bar=*/true);
// warning: argument name 'bar' in comment does not match parameter name 'foo'

The check tries to detect typos and suggest automated fixes for them. It can also insert missing comments for configured argument kinds. so helpful honestly ๐Ÿซถ

Options

  • StrictMode โ€” when false, the check will ignore leading and trailing underscores and case when comparing names โ€” otherwise they are taken into account. Default is false.
  • IgnoreSingleArgument โ€” when true, the check will ignore the single argument. Default is false.
  • CommentAnonymousInitLists โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before anonymous braced-init list arguments such as {} and {1, 2, 3}. Default is false.

Before:

void foo(const std::vector<int> &Dims);

foo({});

After:

void foo(const std::vector<int> &Dims);

foo(/*Dims=*/{});
  • CommentBoolLiterals โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before the boolean literal argument. Default is false.

Before:

void foo(bool TurnKey, bool PressButton);

foo(true, false);

After:

void foo(bool TurnKey, bool PressButton);

foo(/*TurnKey=*/true, /*PressButton=*/false);
  • CommentCharacterLiterals โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before the character literal argument. Default is false.

Before:

void foo(char *Character);

foo('A');

After:

void foo(char *Character);

foo(/*Character=*/'A');
  • CommentFloatLiterals โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before the float/double literal argument. Default is false.

Before:

void foo(float Pi);

foo(3.14159);

After:

void foo(float Pi);

foo(/*Pi=*/3.14159);
  • CommentIntegerLiterals โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before the integer literal argument. Default is false.

Before:

void foo(int MeaningOfLife);

foo(42);

After:

void foo(int MeaningOfLife);

foo(/*MeaningOfLife=*/42);
  • CommentNullPtrs โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before the nullptr literal argument. Default is false.

Before:

void foo(A* Value);

foo(nullptr);

After:

void foo(A* Value);

foo(/*Value=*/nullptr);
  • CommentParenthesizedTemporaries โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before explicit temporary constructions such as Type() and Type(1, 2, 3). Default is false.

Before:

struct Dims {
  Dims();
  Dims(int, int, int);
};

void foo(const Dims &DimsValue);

foo(Dims());
foo(Dims(1, 2, 3));

After:

struct Dims {
  Dims();
  Dims(int, int, int);
};

void foo(const Dims &DimsValue);

foo(/*DimsValue=*/Dims());
foo(/*DimsValue=*/Dims(1, 2, 3));
  • CommentStringLiterals โ€” when true, the check will add argument comments in the format /*ParameterName=*/ right before the string literal argument. Default is false.

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");

CommentTypedInitLists

okay so when this one's true, the check drops argument comments in the /*ParameterName=*/ format right before typed braced-init list arguments, the Type{} kinda situation. Default is false. slay.

Before:

void foo(const std::vector<int> &Dims);

foo(std::vector<int>{});

After:

void foo(const std::vector<int> &Dims);

foo(/*Dims=*/std::vector<int>{});

CommentUserDefinedLiterals

set this to true and the check adds argument comments in the /*ParameterName=*/ format right before the user defined literal argument โœจ Default is false.

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);

๐Ÿ› bugprone-assert-side-effect

Finds assert() calls that have a side effect ๐Ÿ˜ญ

here's the thing bestie โ€” the condition of assert() is only evaluated in debug builds, so if that condition has a side effect, your debug and release builds lowkey behave differently. not the vibe.

โœจ Options โœจ

  • AssertMacros โ€” a comma-separated list of the names of assert macros to be checked. Default is assert,NSAssert,NSCAssert.
  • CheckFunctionCalls โ€” whether to treat non-const member and non-member functions as if they produce side effects. Disabled by default because fr fr 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 suffix Ref, ref, Reference and reference. Default is empty. Heads up: if a name in the list contains the sequence :: it's matched against the qualified type name (i.e. namespace::Type), otherwise it's matched against only the type name (i.e. Type).

โญ bugprone-assignment-in-if-condition

Finds assignments within conditions of if statements ๐Ÿ‘€ Such assignments are bug-prone because they may have been intended as equality tests. iykyk.

This check finds all assignments within if conditions โ€” including ones that are not flagged by -Wparentheses thanks 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-assignment-in-selection-statement

Finds assignments within selection statements ๐ŸŽ€ Such assignments may indicate programmer error because they may have been intended as equality tests.

The selection statements are the conditions of if and loop (for, while, do) statements, the condition of the conditional operator (?:) and any operand of a binary logical operator (&&, ||). The check finds assignments within these contexts if the single expression is an assignment, or the assignment is contained (recursively) in the last operand of a comma (,) operator or in the true and false expressions of a conditional operator. The warning is suppressed if the assignment is placed in extra parentheses โ€” but only if the assignment is the single expression of a condition (of if or a loop statement).

This check corresponds to the CERT rule EXP45-C. Do not perform assignments in selection statements.

Examples

The check emits a warning in the following cases at the indicated locations:

int x = 3;

if (x = 4) // should it be `x == 4` instead of 'x = 4' ?
  x = x + 1;

while ((x <= 11) || (x = 22)) // assignment appears as operand of a logical operator
  x += 2;

do {
  x += 5;
} while ((x > 10) ? (x = 11) : (x > 5)); // assignment in loop condition (from `x = 11`)

for (int i = 0; i == 2, x = 5; ++i) // assignment in loop condition (from last operand of comma)
  foo1(i, x);

for (int i = 0; i == 2, (x = 5); ++i) // assignment is not a single expression, parentheses do not prevent the warning
  foo1(i, x);

int a = (x == 2) || (x = 3); // assignment appears in the operand a logical operator

The following cases do not produce a warning:

if ((x = 1)) { // a single assignment between parentheses
  x += 10;

if ((x = 1) != 0) { // assignment appears in a complex expression and without a logical operator
  ++x;

if (foo(x = 9) && array[x = 8]) { // assignment appears in argument of function call or array index
  ++x;

for (int i = 0; i = 2, x == 5; ++i) // assignment does not take part in the condition of the loop
  foo1(i, x);

๐Ÿ’€ bugprone-bad-signal-to-kill-thread

Finds pthread_kill function calls where a thread is terminated by raising the SIGTERM signal โ€” except 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, like this:

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.

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. periodt.


๐ŸŒŠ bugprone-bool-pointer-implicit-conversion

Checks for conditions based on implicit conversion from a bool pointer to bool ๐Ÿ‘‘

Example:

bool *p;
if (p) {
  // Never used in a pointer-specific way.
}

๐ŸŒˆ bugprone-branch-clone

Checks for repeated branches in if/else if/else chains, consecutive repeated branches in switch statements and identical true and false branches in conditional operators ๐ŸŽ€

if (test_value(x)) {
  y++;
  do_something(x, y);
} else {
  y++;
  do_something(x, y);
}

In this simple example (which could arise e.g. as a copy-paste error) the then and else branches are identical and the code is equivalent to the following shorter and cleaner code:

test_value(x); // can be omitted unless it has side effects
y++;
do_something(x, y);

If this is the intended behavior, then there is no reason to use a conditional statement; otherwise the issue can be solved by fixing the branch that is handled incorrectly. slay.

The check detects repeated branches in longer if/else if/else chains where it would be even harder to notice the problem ๐Ÿ‘€

The check also detects repeated inner and outer if statements that may be a result of a copy-paste error. This check cannot currently detect identical inner and outer if statements if code is between the if conditions. An example is as follows.

void test_warn_inner_if_1(int x) {
  if (x == 1) {    // warns, if with identical inner if
    if (x == 1)    // inner if is here
      ;
  if (x == 1) {    // does not warn, cannot detect
    int y = x;
    if (x == 1)
      ;
  }
}

In switch statements the check only reports repeated branches when they are consecutive, because it's relatively common that the case: labels have some natural ordering and rearranging them would decrease the readability of the code. For example:

switch (ch) {
case 'a':
  return 10;
case 'A':
  return 10;
case 'b':
  return 11;
case 'B':
  return 11;
default:
  return 10;
}

Here the check reports that the 'a' and 'A' branches are identical (and that the 'b' and 'B' branches are also identical), but does not report that the default: branch is also identical to the first two branches. If this is indeed the correct behavior, then it could be implemented as:

switch (ch) {
case 'a':
case 'A':
  return 10;
case 'b':
case 'B':
  return 11;
default:
  return 10;
}

Here the check does not warn for the repeated return 10;, which is good if we want to preserve that 'a' is before 'b' and default: is the last branch ๐Ÿ’…

Switch cases marked with the [[fallthrough]] attribute are ignored.

Finally, the check also examines conditional operators and reports code like:

return test_value(x) ? x : x;

Unlike if statements, the check does not detect chains of conditional operators.

Note: this check also reports situations where branches become identical only after preprocessing. ๐Ÿ˜ญ

๐ŸŒธ bugprone-capturing-this-in-member-variable

Finds lambda captures that capture the this pointer and store it as class members without handling the copy and move constructors and the assignments ๐Ÿ™ˆ

...okay so like, capturing that this pointer in a lambda and then storing the lambda as a class member? dangerous bestie ๐Ÿ’€ because that lambda can totally outlive the object it captured. Especially when the object gets copied or moved โ€” the captured this pointer gets implicitly propagated to the new object. And most of the time people genuinely believe the captured this points to the new object, which is exactly how you end up with bugs. ๐Ÿ™ˆ

struct C {
  C() : Captured([this]() -> C const * { return this; }) {}
  std::function<C const *()> Captured;
};

void foo() {
  C v1{};
  C v2 = v1; // v2.Captured capture v1's 'this' pointer
  assert(v2.Captured() == v1.Captured()); // v2.Captured capture v1's 'this' pointer
  assert(v2.Captured() == &v2); // assertion failed.
}

Possible fixes (pick your fighter โœจ):

  • marking copy and move constructors and assignment operators deleted.
  • using class member method instead of class member variable with function object types.
  • passing this pointer as parameter.

๐ŸŽ€ Options ๐ŸŽ€

  • FunctionWrapperTypes โ€” a semicolon-separated list of names of types. Used to specify function wrapper that can hold lambda expressions. Default is ::std::function;::std::move_only_function;::boost::function.
  • BindFunctions โ€” a semicolon-separated list of fully qualified names of functions that can capture this pointer. Default is ::std::bind;::boost::bind;::std::bind_front;::std::bind_back;::boost::compat::bind_front;::boost::compat::bind_back.

๐Ÿ’… bugprone-casting-through-void

Detects unsafe or redundant two-step casting operations involving void*, which is equivalent to reinterpret_cast as per the C++ Standard. iykyk โœจ

Two-step type conversions via void* are discouraged, and here's the tea on why:

  • 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.

In summary, avoiding two-step type conversions through void* ensures clearer code, maintains essential compiler warnings, and prevents ambiguity and potential runtime errors, particularly in complex inheritance scenarios. If such a cast is wanted, it shall be done via reinterpret_cast, to express the intent more clearly. ๐Ÿ‘‘

Note: it is expected that, after applying the suggested fix and using reinterpret_cast, the check cppcoreguidelines-pro-type-reinterpret-cast will emit a warning. This is intentional: reinterpret_cast is 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 users to carefully analyze whether the usage of reinterpret_cast is safe, in which case the warning may be suppressed. โš ๏ธ

Examples:

using IntegerPointer = int *;
double *ptr;

static_cast<IntegerPointer>(static_cast<void *>(ptr)); // WRONG
reinterpret_cast<IntegerPointer>(reinterpret_cast<void *>(ptr)); // WRONG
(IntegerPointer)(void *)ptr; // WRONG
IntegerPointer(static_cast<void *>(ptr)); // WRONG

reinterpret_cast<IntegerPointer>(ptr); // OK, clearly expresses intent.
                                       // NOTE: dereferencing this pointer violates
                                       // the strict aliasing rules, invoking
                                       // Undefined Behavior.

๐Ÿฆ‹ bugprone-chained-comparison

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. However, this expression does not evaluate as (a < b) && (b < c), which is probably what the developer intended. Instead, it evaluates as (a < b) < c, which may produce unintended results, especially when the types of a, b, and c are different. the way that math just betrays you ๐Ÿ’€

To avoid such errors, the check will issue a warning when a chained comparison operator is detected, suggesting to use parentheses to specify the order of evaluation or to use a logical operator to separate comparison expressions. ๐Ÿซถ

Consider the following examples:

int a = 2, b = 6, c = 4;
if (a < b < c) {
    // This block will be executed
}

In this example, the developer intended to check if a is less than b and b is less than c. However, the expression a < b < c is equivalent to (a < b) < c. Since a < b is true, the expression (a < b) < c is evaluated as 1 < c, which is equivalent to true < c and is invalid in this case as b < c is false.

Even that above issue could be detected as comparison of int to bool, there is more dangerous example:

bool a = false, b = false, c = true;
if (a == b == c) {
    // This block will be executed
}

In this example, the developer intended to check if a, b, and c are all equal. However, the expression a == b == c is evaluated as (a == b) == c. Since a == b is true, the expression (a == b) == c is evaluated as true == c, which is equivalent to true == true. This comparison yields true, even though a and b are false, and are not equal to c. ๐Ÿ˜ฑ

To avoid this issue, the developer can use a logical operator to separate the comparison expressions, like this:

if (a == b && b == c) {
    // This block will not be executed
}

Alternatively, use of parentheses in the comparison expressions can make the developer's intention more explicit and help avoid misunderstanding.

if ((a == b) == c) {
    // This block will be executed
}

๐ŸŽ€ Options ๐ŸŽ€

  • IgnoreMacros โ€” If true, the check will not warn on chained comparisons inside macros. Default is false.

๐ŸŒธ bugprone-command-processor

Flags calls to system(), popen(), and _popen(), which execute a command processor. It does not flag calls to system() with a null pointer argument, as such a call checks for the presence of a command processor but does not actually attempt to execute a command. ๐Ÿ–ฅ๏ธโœจ

๐Ÿ“š References ๐Ÿ“š

This check corresponds to the CERT C Coding Standard rule ENV33-C. Do not call system().


๐Ÿ‘‘ bugprone-compare-pointer-to-member-virtual-function

Detects unspecified behavior about equality comparison between pointer to member virtual function and anything other than 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.
}

Provide warnings on equality comparisons involve pointers to member virtual function or variables which is potential pointer to member virtual function and any entity other than a null-pointer constant. โš ๏ธ

In certain compilers, virtual function addresses are not conventional pointers but instead consist of offsets and indexes within a virtual function table (vtable). Consequently, these pointers may vary between base and derived classes, leading to unpredictable behavior when compared directly. This issue becomes particularly challenging when dealing with pointers to pure virtual functions, as they may not even have a valid address, further complicating comparisons. fr fr, chaos ๐Ÿ˜ญ

Instead, it is recommended to utilize the typeid operator or other appropriate mechanisms for comparing objects to ensure robust and predictable behavior in 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, thereby improving the overall stability and maintainability of your code. In scenarios involving pointers to member virtual functions, it's only advisable to employ nullptr for comparisons. ๐Ÿ’–

๐Ÿ” Limitations

Does not analyze values stored in a variable. For variable, only analyze all virtual methods in the same class or struct and diagnose when assigning a pointer to member virtual function to this variable is possible.


๐ŸŽ€ bugprone-copy-constructor-init

Finds copy constructors where the constructor doesn't call the copy constructor of the base class. ๐Ÿซถ

class Copyable {
public:
  Copyable() = default;
  Copyable(const Copyable &) = default;

  int memberToBeCopied = 0;
};

class X2 : public Copyable {
  X2(const X2 &other) {} // Copyable(other) is missing
};

Also finds copy constructors where the constructor of the base class don't have parameter.

class X3 : public Copyable {
  X3(const X3 &other) : Copyable() {} // other is missing
};

Failure to properly initialize base class sub-objects during copy construction can result in undefined behavior, crashes, data corruption, or other unexpected outcomes. not the data corruption!! ๐Ÿ’€ The check ensures that the copy constructor of a derived class properly calls the copy constructor of the base class, helping to prevent bugs and improve code quality. โœจ

๐Ÿ” Limitations

  • It won't generate warnings for empty classes, as there are no class members (including base class sub-objects) to worry about.
  • It won't generate warnings for base classes that have copy constructor private or deleted.
  • It won't generate warnings for base classes that are initialized using other non-default constructor, as this could be intentional.

The check also suggests a fix-its in some cases. ๐Ÿ”ง


๐ŸŒˆ bugprone-copy-constructor-mutates-argument

Finds assignments to the copied object and its direct or indirect members in copy constructors and copy assignment operators. ๐Ÿ˜ฌ

This check corresponds to the CERT C Coding Standard rule OOP58-CPP. Copy operations must not mutate the source object. periodt ๐Ÿ’…


๐Ÿฆ‹ bugprone-crtp-constructor-accessibility

Detects error-prone Curiously Recurring Template Pattern usage, when the CRTP can be constructed outside itself and the derived class. ๐Ÿงšโœจ

like, okay, so the CRTP is that iconic idiom where a class derives from a template class and passes itself in as the template argument ๐ŸŒ€ โ€” and the whole vibe check here is: if a class is meant to be a base in this idiom, make sure it can only be instantiated when the derived class is literally its template argument.

Example, aka the slay version:

template <typename T> class CRTP {
private:
  CRTP() = default;
  friend T;
};

class Derived : CRTP<Derived> {};

and like, below are some โœจcommon mistakesโœจ that let the idiom get broken wide open ๐Ÿ™ˆ

if the constructor of a class meant for CRTP is public, then bestieโ€ฆ anyone can construct that class on its own. not the vibe:

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 โ€” okay, cute โ€” BUT it can fade an error when a different class is used as the template parameter instead of the derived one ๐Ÿ˜ญ like:

template <typename T> class CRTP {
protected:
  CRTP() = default;
};

class Good : CRTP<Good> {};
Good GoodInstance;

class Bad : CRTP<Good> {};
Bad BadInstance;

so like, to make sure no accidental instantiation ever happens, the best practice is to make the constructor private and declare the derived class as friend ๐Ÿ‘‘ note that, as a tradeoff, this also gives the derived class access to every other private member of the CRTP. BUT โ€” plot twist โ€” constructors can still be public or protected if they are deleted ๐Ÿ’…

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 a fix-its in some cases โœจ (yes, that's what it says, and yes we're keeping it)


๐ŸŒธ bugprone-dangling-handle

detects dangling references in value handles like std::string_view ๐Ÿ’€ these dangling references can be a result of constructing handles from temporary values, where the temporary gets destroyed super soon after the handle is created. gone. poof. ๐Ÿฆ‹

Examples, bestie:

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 only std::basic_string_view, std::experimental::basic_string_view and std::span are considered.

๐Ÿฆ‹ bugprone-default-operator-new-on-overaligned-type

flags uses of default operator new where the type has extended alignment (an alignment greater than the fundamental alignment) ๐Ÿ”ฅ

the default operator new is guaranteed to provide the correct alignment if the requested alignment is less or equal to the fundamental alignment. Only cases are detected (by design) where the operator new is not user-defined and is not a placement new โ€” the reason being that in these cases we assume the user provided the correct memory allocation ๐Ÿซถ

๐Ÿ’œ References

This check corresponds to the CERT C++ Coding Standard rule MEM57-CPP. Avoid using default operator new for over-aligned types.


โญ bugprone-derived-method-shadowing-base-method

finds derived class methods that shadow a (non-virtual) base class method ๐Ÿ‘€

for it to count as "shadowing", the methods have to have the same signature (i.e. the same name, same number of parameters, same parameter types, etc). Only checks public, non-templated methods.

the example below is bugprone because consumers of the Derived class will expect the reset method to do the work of Base::reset() in addition to the extra work required to reset the Derived class ๐Ÿ˜ญ common fixes include:

  • making the reset method polymorphic
  • re-naming Derived::reset if it's not meant to intersect with Base::reset
  • using using Base::reset to change the access specifier

this is also a violation of the Liskov Substitution Principle (rude of it, honestly):

struct Base {
  void reset() {/* reset the base class */};
};

struct Derived : public Base {
  void reset() {/* reset the derived class, but not the base class */};
};

๐Ÿงš bugprone-dynamic-static-initializers

finds instances of static variables that are dynamically initialized in header files ๐ŸŽ€

this can pose problems in certain multithreaded contexts. For example, when disabling compiler generated synchronization instructions for static variables initialized at runtime (e.g. by -fno-threadsafe-statics), even if a particular project takes the necessary precautions to prevent race conditions during initialization by providing their own synchronization, header files included from other projects may not. Therefore, such a check is helpful for ensuring that disabling compiler generated synchronization for static variable initialization will not 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 the possibility that k will be double initialized, creating a race condition ๐Ÿ’€ no cap.


๐Ÿ“ 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 might not be what the developer of the function intended ๐Ÿ˜ญ

more elaborate and type-safe constructs, such as opaque typedefs or strong types, should be used instead, to prevent a mistaken order of arguments โœจ

struct Coord2D { int X; int Y; };
void drawPoint(const Coord2D Pos) { /* ... */ }

FILE *open(const Path &Dir, const Filename &Name, Flags Mode) { /* ... */ }

because of the potentially elaborate refactoring and API-breaking that's necessary to strengthen the type safety of a project, no automatic fix-its are offered ๐Ÿซถ

โœจ Options โœจ

๐Ÿ’… Extension/relaxation options

relaxation (or extension) options can be used to broaden the scope of the analysis and fine-tune the enabling of more mixes between types. Some mixes may depend on coding style or preference specific to a project, however, it should be noted that enabling all of these relaxations models the way of mixing at call sites the most. These options are expected to make the check report for more functions, and report longer mixable ranges ๐Ÿ‘‘

  • QualifiersMix โ€” whether to consider parameters of some cvr-qualified T and a differently cvr-qualified T (i.e. T and const T, const T and volatile T, etc.) mixable between one another. If false, the check will consider differently qualified types unmixable. True turns the warnings on. Defaults to false.

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 type T and U mixable if there exists an implicit conversion from T to U and U to T. If false, the check will not consider implicitly convertible types for mixability. True turns warnings for implicit conversions on. Defaults to true.

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. from int to const 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 whether QualifiersMix was enabled. for example, the following code snippet will only produce a diagnostic if both QualifiersMix and ModelImplicitConversions are enabled: void fun2(int Int, const double Double) { /* ... */ }

๐ŸŒˆ Filtering options

filtering options can be used to lessen the size of the diagnostics emitted by the checker โ€” whether the aim is to ignore certain constructs or just dampen the noisiness ๐ŸŽ€

  • MinimumLength โ€” the minimum length required from an adjacent parameter sequence to be diagnosed. Defaults to 2. Might be any positive integer greater or equal to 2. If 0 or 1 is given, the default value 2 will 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 which type, as written in the source code, end with an element of this option will be ignored. The value is a ;-separated list of names. This option is case-sensitive! ๐Ÿ’…

So like, the default ignore-list is genuinely a whole mood, bestie โ€” here's the roster (and yeah, some of these show up twice in the original list, that's just how it is, no cap):

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

And every one of those also gets ignored in its lowercase-initial variant (iykyk). Also part of the default value: _Bool โ€” but not _bool, bestie, don't get it twisted ๐Ÿ’…. Such a specific little detail and honestly? Obsessed.


๐ŸŒธ SuppressParametersUsedTogether ๐ŸŒธ

This one quiets down the diagnostics about parameters that get used together, or in a similar way, inside the function's body. Defaults to true. Set it to false and the heuristics go sleep ๐Ÿ˜ด โ€” turned off, periodt.

Right now these are the heuristics that are implemented, each one suppressing the warning about the parameter pair it catches:

  • The parameters are used inside the same expression โ€” like f(a, b), or a < b.
  • The parameters get passed further to the same function, into the same parameter of that function, with the same overload. Example: f(a, 1) and f(b, 2) both going into some f(T, int).
NOTE: ๐Ÿ’€ The check does not do path-sensitive analysis, so "same function" here literally means the same function declaration. If you call the same member function of a type on two distinct instances with those parameters, it still counts as "same function". Sorry bestie, that's just the rules.
  • The same member field is accessed on both parameters, or the same member method is called on both โ€” like a.foo() and b.foo() ๐Ÿซถ.
  • Separate return statements each return one of the parameters, on different code paths.

๐ŸŽ€ NamePrefixSuffixSilenceDissimilarityThreshold ๐ŸŽ€

This is the number of characters that two parameter names are allowed to differ by โ€” on either the head or the tail end โ€” while the rest of the name stays the same, for the warning about those two parameters to get silenced. Defaults to 1. It can be any positive integer. If it's 0, the name-based filtering heuristic is fully turned off, no filter, we die like real ones ๐Ÿ’€.

Use this option to silence warnings about parameters where the naming scheme itself is telling you the order of those parameters doesn't matter.

Okay so example time, because this is actually so cute: the parameters LHS and RHS are 1-dissimilar suffixes of each other โ€” L and R are the different characters, while HS is the shared common suffix. Similarly, the 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 straight-up won't be reported. Slay.


๐Ÿฆ‹ Limitations ๐Ÿฆ‹

Okay, real talk with a gentle vibe check: this check is designed to check function signatures, and that's it. That's the whole assignment.

It does not investigate functions that the compiler generates in a context that can only be determined from a call site. Those cases include:

  • variadic functions,
  • functions in C code that don't have an argument list,
  • C++ template instantiations.

Most of these cases, even when they're totally swappable from a caller's standpoint, have no way of getting "fixed" at the definition point. And for C++ templates specifically โ€” only primary template definitions and explicit specializations get matched and analyzed. Ngl that's just how the cookie crumbles ๐Ÿ“.

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.
}

Because of that limitation above, parameters whose type further depends on template instantiations to prove they mix with another parameter's type โ€” those don't get diagnosed either. ๐Ÿ˜ญ

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) { /* ... */ }

And when we're in implicit-conversion territory (i.e. ModelImplicitConversions is enabled โœจ), the modelling the check does will warn if the parameters are swappable and the swapped order matches implicit conversions. What it does not do is model whether there's some unrelated third type that both parameters could be given as in a function call. Which means in the example below, even though strs() clearly has the possibility of being called with swapped arguments (as long as those arguments are string literals), it will not be warned about. Iconic behaviour, 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 issues with empty catch statements and suggests how to address them. Look at this little crime scene:

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 exist to handle exceptions thrown during program execution. When an exception is thrown, the program jumps to the nearest catch statement matching the type of the exception.

Empty catch statements โ€” also known as "swallowing" exceptions โ€” catch the exception and then do absolutely nothing with it ๐Ÿ™ˆ. Which means the exception is not handled properly, and the program keeps running like nothing happened. That 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 keep behaving in unexpected ways.
  • Security Issues: ignoring exceptions can lead to security issues, such as buffer overflows or null pointer dereferences. Hackers can exploit these vulnerabilities to gain access to sensitive data or execute malicious code. ๐Ÿ’€
  • Poor Code Quality: empty catch statements can indicate poor code quality and a lack of attention to detail. That can make the codebase difficult to maintain and update, leading to longer development cycles and increased costs.
  • Unreliable Code: code that ignores exceptions is often unreliable and can lead to unpredictable behavior. That causes frustration for users and erodes trust in the software.

To avoid these issues, developers should always handle exceptions properly. Which means either fixing the underlying issue that caused the exception, or propagating the exception up the call stack to a higher-level handler. Even 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 handle it inside the catch block. That 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 travel up the call stack, it should be re-thrown, or a new exception should be thrown.

Here's an example:

try {
  // Some code that can throw an exception
} catch (const std::exception& ex) {
  // Re-throw the exception
  throw;
}

In some cases, catching the exception at this level may not be necessary at all, and it may be appropriate to just let the exception propagate up the call stack. You do that simply by not using a try/catch block. ๐Ÿงš

Here's an example:

void function() {
  // Some code that can throw an exception
}

void callerFunction() {
  try {
    function();
  } catch (const std::exception& ex) {
    // Handling exception on higher level
    std::cerr << "Exception caught: " << ex.what() << std::endl;
  }
}

Another potential solution for avoiding empty catch statements is to modify the code so the exception isn't thrown in the first place. You can do that 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.

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 keep their code robust, secure, and maintainable. Periodt. ๐Ÿ‘‘


โญ Options โญ

  • IgnoreCatchWithKeywords โ€” this option lets you ignore specific catch statements that contain certain keywords. If a catch statement's body contains (case-insensitive) any of the keywords listed in this semicolon-separated option, then that catch is ignored and no warning gets raised ๐Ÿซถ. Default value: @TODO;@FIXME.
  • AllowEmptyCatchForExceptions โ€” okay so this one lets you ignore empty catch statements for specific exception types ๐Ÿ’… by default the check is gonna raise a warning whenever it sniffs out an empty catch block, no matter what exception type is being caught. But sometimes โ€” like when a dev is intentionally ignoring certain exceptions, or handling them some other way entirely โ€” you lowkey want to allow empty catches for particular exception types. So to configure it, you give a semicolon-separated list of exception type names ๐ŸŽ€ if an exception type name from that list gets caught in an empty catch statement, no warning gets raised. Default value: empty string โœจ

๐Ÿฆ‹ bugprone-exception-copy-constructor-throws

Checks whether a thrown object's copy constructor can throw ๐Ÿ˜ญ

Exception objects are required to be copy constructible in C++ โ€” that's just the rules, bestie. However, an exception's copy constructor should not throw, to avoid potential issues when unwinding the stack. Because if an exception gets thrown during stack unwinding (say, from a copy constructor of an exception object), the program will terminate via std::terminate ๐Ÿ’€ and that's not the vibe.

class SomeException {
public:
  SomeException() = default;
  SomeException(const SomeException&) { /* may throw */ }
};

void f() {
  throw SomeException();  // warning: thrown exception type's copy constructor can throw
}

References

This check corresponds to the CERT C++ Coding Standard rule ERR60-CPP. Exception objects must be nothrow copy constructible ๐Ÿ”ฅ


๐Ÿ‘‘ bugprone-exception-escape

Finds functions which may throw an exception directly or indirectly, but they should not ๐Ÿซถ the functions that are supposed to keep it together and not throw are:

  • Destructors
  • Move constructors
  • Move assignment operators
  • The main() functions
  • swap() functions
  • iter_swap() functions
  • iter_move() functions
  • Functions marked with throw() or noexcept
  • Other functions given as option

A destructor throwing an exception may result in undefined behavior, resource leaks, or unexpected termination of the program ๐Ÿ˜ญ throwing from a move constructor or move assignment also may result in undefined behavior or a resource leak. The swap() operations are expected to be non-throwing most of the cases, and they're always possible to implement in a non-throwing way. Non-throwing swap() operations are also used to create move operations. And a throwing main() function also results in unexpected termination, which is giving "my program just vanished" energy ๐Ÿ’€

Functions declared explicitly with noexcept(false) or throw(exception) are excluded from the analysis โ€” because even though it's not recommended for functions like swap(), main(), move constructors, move assignment operators and destructors, it's a clear indication of the developer's intention and should be respected โœ‹ To check if these special functions are marked as potentially throwing, the check bugprone-unsafe-to-allow-exceptions can be used ๐Ÿ‘€

โš ๏ธ WARNING! This check may be expensive on large source files. Like, it's gonna take a minute, grab a snack ๐Ÿ“

โœจ Options โœจ

  • CheckDestructors โ€” when true, destructors are analyzed to not throw exceptions. Default value is true.
  • CheckMoveMemberFunctions โ€” when true, move constructors and move assignment operators are analyzed to not throw exceptions. Default value is true.
  • CheckMain โ€” when true, the main() function is analyzed to not throw exceptions. Default value is true.
  • CheckNothrowFunctions โ€” when true, functions marked with noexcept or throw() exception specifications are analyzed to not throw exceptions. Default value is true.
  • CheckedSwapFunctions โ€” comma-separated list of swap function names which should not throw exceptions. Default value is swap,iter_swap,iter_move.
  • FunctionsThatShouldNotThrow โ€” comma separated list containing function names which should not throw ๐ŸŽฏ an example value for this parameter can be WinMain, which adds the function WinMain() in the Windows API to the list of functions which should not throw. Default value is an empty string.
  • IgnoredExceptions โ€” comma separated list containing type names which are not counted as thrown exceptions in the check. Default value is an empty string.
  • TreatFunctionsWithoutSpecificationAsThrowing โ€” determines which functions are considered as throwing if they do not have an explicit exception specification. It can be set to the following values:
    • None โ€” the check will consider functions without an explicit exception specification as throwing only if they have a visible definition which can be deduced to throw.
    • OnlyUndefined โ€” the check will consider functions with only a declaration available and no visible definition as throwing.
    • All โ€” the check will consider all functions without an explicit exception specification (such as noexcept) as throwing, even if they have a visible definition and do not contain any throwing statements.

Default value is None.


๐ŸŒˆ bugprone-float-loop-counter

Flags for loops where the induction expression has a floating-point type ๐Ÿšฉ like bestie, floats as loop counters is not the move.

References

This check corresponds to the CERT C Coding Standard rule FLP30-C. Do not use floating-point variables as loop counters โœจ


๐ŸŽ€ bugprone-fold-init-type

The check flags type mismatches in folds that might result in loss of precision ๐Ÿ˜ฌ

The check supports the following functions:

  • std::accumulate
  • std::reduce
  • std::inner_product

These functions fold an input range into an initial value using the type of the latter. By default, std::accumulate and std::reduce use operator+ while std::inner_product uses operator+ and operator* ๐Ÿ’ซ this can cause loss of precision through:

  • Truncation: the following code uses a floating point range and an int initial value, so truncation will happen at every application of operator+ and the result will be 0, which might not be what the user expected ๐Ÿ™ˆ
auto a = {0.5f, 0.5f, 0.5f, 0.5f};
return std::accumulate(std::begin(a), std::end(a), 0);
  • Overflow: the following code also returns 0 ๐Ÿ’€
auto a = {65536LL * 65536 * 65536};
return std::accumulate(std::begin(a), std::end(a), 0);

The check handles overloads with the following transparent standard functors ๐Ÿงš

  • std::plus
  • std::minus
  • std::multiplies
  • std::divides
  • std::bit_and
  • std::bit_or
  • std::bit_xor

๐Ÿฆ‹ bugprone-forward-declaration-namespace

Checks if an unused forward declaration is in a wrong namespace ๐Ÿ‘€

The check inspects all unused forward declarations and checks if there's any declaration/definition with the same name existing โ€” which could indicate that the forward declaration is in a potentially wrong namespace:

namespace na { struct A; }
namespace nb { struct A {}; }
nb::A a;
// warning : no definition found for 'A', but a definition with the same name
// 'A' found in another namespace 'nb::'

This check can only generate warnings, but it can't suggest a fix at this point โ€” it's giving "I see the problem but I can't fix it for you" energy ๐Ÿ˜ญ


๐Ÿ’œ bugprone-forwarding-reference-overload

The check looks for perfect forwarding constructors that can hide copy or move constructors ๐Ÿ•ต๏ธ if a non-const lvalue reference is 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 will be called, which can be confusing, fr fr. For a detailed description of this issue see: Scott Meyers, Effective Modern C++, Item 26 ๐Ÿ“š

Consider the following example:

class Person {
public:
  // C1: perfect forwarding ctor
  template<typename T>
  explicit Person(T&& n) {}

  // C2: perfect forwarding ctor with parameter default value
  template<typename T>
  explicit Person(T&& n, int x = 1) {}

  // C3: perfect forwarding ctor guarded with enable_if
  template<typename T, typename X = enable_if_t<is_special<T>, void>>
  explicit Person(T&& n) {}

  // C4: variadic perfect forwarding ctor guarded with enable_if
  template<typename... A,
    enable_if_t<is_constructible_v<tuple<string, int>, A&&...>, int> = 0>
  explicit Person(A&&... a) {}

  // C5: perfect forwarding ctor guarded with requires expression
  template<typename T>
  requires requires { is_special<T>; }
  explicit Person(T&& n) {}

  // C6: perfect forwarding ctor guarded with concept requirement
  template<Special T>
  explicit Person(T&& n) {}

  // (possibly compiler generated) copy ctor
  Person(const Person& rhs);
};

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 nothing the perfect forwarding constructor could hide in this 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 you avoid the widening afterwards ๐Ÿ’…

This is mainly useful when operating on very large buffers, bestie. 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 ๐Ÿ˜ญ and that is not the slay ending you wanted.

okay so, bestie, 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 youโ€™re suggesting fix-its for C++ code, should C++-style static_cast<>()โ€™s be suggested, or C-style casts? Defaults to true. ๐Ÿ’…
  • UseCXXHeadersInCppSources โ€” when suggesting that the appropriate header gets included in C++ code, should the <cstddef> header be suggested, or <stddef.h>? Defaults to true. ๐ŸŽ€
  • IgnoreConstantIntExpr โ€” if the multiplication operands are compile-time constants (like literals or are constexpr) and fit within the source expression type, do not emit a diagnostic or suggested fix. It only considers expressions where the source expression is a signed integer type. Defaults to false. โœจ

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

Okay so this one checks for inaccurate use of the erase() method. ๐Ÿ˜ญ

Algorithms like remove() do not actually remove any element from the container โ€” they return an iterator to the first redundant element at the end of the container. Those redundant elements have to be removed with the erase() method. This check warns when not all of the elements will be removed because you used an inappropriate overload. ๐Ÿ’…

For example, the following code erases only one element:

std::vector<int> xs;
...
xs.erase(std::remove(xs.begin(), xs.end(), 10));

Call the two-argument overload of erase() to remove the subrange:

std::vector<int> xs;
...
xs.erase(std::remove(xs.begin(), xs.end(), 10), xs.end());

๐Ÿ” bugprone-inc-dec-in-conditions

Detects when a variable is both incremented/decremented and referenced inside a complex condition, and suggests moving them outside so the variableโ€™s value doesnโ€™t get all ambiguous. ๐Ÿซถ

When a variable is modified and also used in a complex condition, it can lead to unexpected behavior. The side-effect of changing the variableโ€™s value within the condition can make the code difficult to reason about. Additionally, the developerโ€™s intended timing for the modification of the variable may not be clear, leading to misunderstandings and errors. This can be particularly problematic when the condition involves logical operators like && and ||, where the order of evaluation can further complicate the situation. ๐Ÿ˜ญ

Consider the following example:

int i = 0;
// ...
if (i++ < 5 && i > 0) {
  // do something
}

In this example, the result of the expression may not be what the developer intended. The original intention of the developer could be to increment i after the entire condition is evaluated, but in reality, i will be incremented before i > 0 is executed. This can lead to unexpected behavior and bugs in the code. To fix this issue, the developer should separate the increment operation from the condition and perform it separately. For example, they can increment i in a separate statement before or after the condition is evaluated. This ensures that the value of i is predictable and consistent throughout the code. โœจ

int i = 0;
// ...
i++;
if (i <= 5 && i > 0) {
  // do something
}

Another common issue occurs when multiple increments or decrements are performed on the same variable inside a complex condition. For example:

int i = 4;
// ...
if (i++ < 5 || --i > 2) {
  // do something
}

There is a potential issue with this code due to the order of evaluation in C++. The || operator used in the condition statement guarantees that if the first operand evaluates to true, the second operand will not be evaluated. This means that if i were initially 4, the first operand i < 5 would evaluate to true and the second operand i > 2 would not be evaluated. As a result, the decrement operation --i would not be executed and i would hold value 5, which may not be the intended behavior for the developer. ๐Ÿคฏ

To avoid this potential issue, both the increment and decrement operations on i should be moved outside the condition statement. ๐Ÿ’…

๐Ÿงš bugprone-incorrect-enable-if

Detects incorrect usages of std::enable_if that donโ€™t name the nested type type. ๐Ÿ‘‘

C++11 introduced std::enable_if as a convenient way to leverage SFINAE. One form of using std::enable_if is to declare an unnamed template type parameter with a default type equal to typename std::enable_if<condition>::type. If the author forgets to name the nested type type, then the code will always consider the candidate template even if the condition is not met. ๐Ÿ˜ญ

Below are some examples of code using std::enable_if correctly, plus incorrect examples that this check flags. ๐Ÿšฉ

template <typename T, typename = typename std::enable_if<T::some_trait>::type>
void valid_usage() { ... }

template <typename T, typename = std::enable_if_t<T::some_trait>>
void valid_usage_with_trait_helpers() { ... }

// The below code is not a correct application of SFINAE. Even if
// T::some_trait is not true, the function will still be considered in the
// set of function candidates. It can either incorrectly select the function
// when it should not be a candidates, and/or lead to hard compile errors
// if the body of the template does not compile if the condition is not
// satisfied.
template <typename T, typename = std::enable_if<T::some_trait>>
void invalid_usage() { ... }

// The tool suggests the following replacement for 'invalid_usage':
template <typename T, typename = typename std::enable_if<T::some_trait>::type>
void fixed_invalid_usage() { ... }

C++14 introduced the trait helper std::enable_if_t which reduces the likelihood of this error. C++20 introduces constraints, which generally supersede the use of std::enable_if. See modernize-type-traits for another tool that will replace std::enable_if with std::enable_if_t, and see modernize-use-constraints for another tool that replaces std::enable_if with C++20 constraints. Consider these newer mechanisms where possible. ๐ŸŒˆ

๐Ÿงจ bugprone-incorrect-enable-shared-from-this

Detect classes or structs that do not publicly inherit from std::enable_shared_from_this, because unintended behavior will otherwise occur when calling shared_from_this. ๐Ÿ’€

Consider the following code:

#include <memory>

// 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. ๐Ÿ˜ญ

๐ŸŽฏ 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:

  1. It is unnecessarily slow. ๐ŸŒ
  2. It is incorrect. The number 0.499999975 (smallest representable float number below 0.5) rounds to 1.0. Even worse behavior for negative numbers where both -0.5f and -1.4f both round to 0.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 bool types,
  • call expressions of integral or bool types, and
  • explicit cast expressions to integral or bool types,

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-invalid-enum-default-initialization

Detects default initialization (to 0) of variables with enum type where the enum has no enumerator with value of 0. ๐Ÿ˜ฑ

In C++ a default initialization is performed if a variable is initialized with initializer list or in other implicit ways, and no value is specified at the initialization. In such cases the value 0 is used for the initialization. This also applies to enumerations even if it does not have an enumerator with value 0. In this way a variable with the enum type may contain initially an invalid value (if the program expects that it contains only the listed enumerator values). ๐Ÿ’€

...and here's the โœจsituationshipโœจ: the check only yells at you when an enum variable is actually default-initialized (which is not the same as "never initialized", bestie, and honestly that distinction ate) AND the enum has no enumerator rocking the value 0. The type can be a scoped or non-scoped enum, and listen โ€” unions are not handled by this check (even if they've got a member of enumeration type). Not its job, not its problem.

Also super important ๐Ÿ’…: the enum std::errc is always ignored, because it's expected to be default-initialized even though it doesn't define an enumerator with the value 0. It gets a permanent hall pass, iykyk.

enum class Enum1: int {
  A = 1,
  B
};

enum class Enum0: int {
  A = 0,
  B
};

void f() {
  Enum1 X1{}; // warn: 'X1' is initialized to 0
  Enum1 X2 = Enum1(); // warn: 'X2' is initialized to 0
  Enum1 X3; // no warning: 'X3' is not initialized
  Enum0 X4{}; // no warning: type has an enumerator with value of 0
}

struct S1 {
  Enum1 A;
  S(): A() {} // warn: 'A' is initialized to 0
};

struct S2 {
  int A;
  Enum1 B;
};

S2 VarS2{}; // warn: member 'B' is initialized to 0

And the check doesn't stop at C++ โ€” it applies to initialization of arrays or structures with initialization lists in C code too. In those cases, elements that aren't specified in the list (and have enum type) are set to 0. Sneaky little zeros, every time ๐Ÿ˜ญ.

enum Enum1 {
  Enum1_A = 1,
  Enum1_B
};
struct Struct1 {
  int a;
  enum Enum1 b;
};

enum Enum1 Array1[2] = {Enum1_A}; // warn: omitted elements are initialized to 0
enum Enum1 Array2[2][2] = {{Enum1_A}, {Enum1_A}}; // warn: last element of both nested arrays is initialized to 0
enum Enum1 Array3[2][2] = {{Enum1_A, Enum1_A}}; // warn: elements of second array are initialized to 0

struct Struct1 S1 = {1}; // warn: element 'b' is initialized to 0

Options

  • IgnoredEnums โ€” semicolon-separated list of regexes specifying enums for which this check won't be enforced. Default is ::std::errc. Yes, regex. Yes, semicolons. Go off ๐ŸŽ€

๐ŸŒˆ bugprone-lambda-function-name ๐ŸŒˆ

Ok so this one catches you trying to get the name of a function from inside a lambda expression ๐Ÿ’€. And here's the tea: the name of a lambda is always something like operator(), which is almost never what you actually wanted. It's giving "who is she" energy.

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 (aka the dream โœจ):

Called from FancyFunction
Now called from FancyFunction

Options

  • IgnoreMacros โ€” the value true specifies that attempting to get the name of a function from within a macro should not be diagnosed. The default value is false. So by default, macros get no mercy ๐Ÿซถ

๐Ÿงš bugprone-macro-parentheses ๐Ÿงš

Finds macros that can have unexpected behavior due to missing parentheses. And bestie, "unexpected behavior" is doing a lot of heavy lifting in that sentence.

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. Chaos. Absolute chaos.

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 โ€” main character energy for your expression.

It is also recommended to surround macro arguments in the replacement list with parentheses. This ensures that the argument value is calculated properly. Protect the arguments, they're precious ๐ŸŒธ.

This check corresponds to the CERT C Coding Standard rule PRE02-C. Macro replacement lists should be parenthesized. Periodt.


๐Ÿ’œ bugprone-macro-repeated-side-effects ๐Ÿ’œ

Checks for repeated argument with side effects in macros. Short, sweet, and absolutely here to ruin your day if you wrote MAX(i++, j++) and thought you ate. (Default-initialize your side effects responsibly, bestie.)


๐Ÿฆ‹ bugprone-misleading-setter-of-reference ๐Ÿฆ‹

Finds setter-like member functions that take a pointer parameter and set a reference member of the same class with the pointed value. It's the "trust me bro, it's a setter" of C++ ๐Ÿซ .

The check detects member functions that take a single pointer parameter, and contain a single expression statement that dereferences the parameter and assigns the result to a data member with a reference type.

The fact that a setter function takes a pointer might cause the belief that an internal reference (if it would be a pointer) is changed instead of the pointed-to (or referenced) value. The audacity of the vibes ๐Ÿ’€.

Example:

class MyClass {
  int &InternalRef;  // non-const reference member
public:
  MyClass(int &Value) : InternalRef(Value) {}

  // Warning: This setter could lead to unintended behaviour.
  void setRef(int *Value) {
    InternalRef = *Value;  // This assigns to the referenced value, not changing what InternalRef references.
  }
};

int main() {
  int Value1 = 42;
  int Value2 = 100;
  MyClass X(Value1);

  // This might look like it changes what InternalRef references to,
  // but it actually modifies Value1 to be 100.
  X.setRef(&Value2);
}

Possible fixes โœจ (pick your fighter):

  • Change the parameter type of the "set" function to non-pointer type (for example, a const reference).
  • Change the type of the member variable to a pointer and in the "set" function assign a value to the pointer (without dereference).

๐Ÿ“ bugprone-misplaced-operator-in-strlen-in-alloc ๐Ÿ“

Finds cases where 1 is added to the string in the argument to strlen(), strnlen(), strnlen_s(), wcslen(), wcsnlen(), and wcsnlen_s() instead of to the result โ€” and then that value is used as an argument to a memory allocation function (malloc(), calloc(), realloc(), alloca()) or the new[] operator in C++. It's the classic "off-by-one but make it fashion" ๐Ÿ˜ญ.

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. So parentheses are your get-out-of-jail card, no cap.

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 (yes, the double parens are the whole point ๐Ÿ‘‘):

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. Sir, that is not the memory you think it is ๐Ÿ’€. 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. Either way it's not slaying.

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);
}

So float girlies, you're safe for now ๐ŸŒธ.

Options

  • CheckImplicitCasts โ€” if true, enables detection of implicit casts. Default is false.

๐ŸŽ€ bugprone-missing-end-comparison ๐ŸŽ€

Finds instances where the result of a standard algorithm is used in a Boolean context without being compared to the end iterator. This one is so real, because it's almost always a bug and it looks totally fine ๐Ÿ˜ญ.

Standard algorithms such as std::find, std::search, and std::lower_bound return an iterator to the element if found, or the end iterator otherwise.

Using the result directly in a Boolean context (like an if statement) is almost always a bug, as it only checks if the iterator itself evaluates to true โ€” which may always be true for many iterator types. Iconic. Devastating. A trap. ๐Ÿ’€

Examples:

void example() {
  int arr[] = {1, 2, 3};
  int* begin = std::begin(arr);
  int* end = std::end(arr);

  if (std::find(begin, end, 2)) {
    // ...
  }

  // Fixed by the check:
  if ((std::find(begin, end, 2) != end)) {
    // ...
  }

  // C++20 ranges:
  int v[] = {1, 2, 3};
  if (std::ranges::find(v, 2)) {
    // ...
  }

  // Fixed by the check:
  if ((std::ranges::find(v, 2) != std::ranges::end(v))) {
    // ...
  }
}

The check also handles range-based algorithms introduced in C++20 ๐Ÿฆ‹.

Supported algorithms (the whole squad):

  • std::adjacent_find
  • std::find
  • std::find_end
  • std::find_first_of
  • std::find_if
  • std::find_if_not
  • std::is_sorted_until
  • std::lower_bound
  • std::max_element
  • std::min_element
  • std::partition_point
  • std::search
  • std::search_n
  • std::upper_bound
  • std::ranges::adjacent_find
  • std::ranges::find
  • std::ranges::find_first_of
  • std::ranges::find_if
  • std::ranges::find_if_not
  • std::ranges::is_sorted_until
  • std::ranges::lower_bound
  • std::ranges::max_element
  • std::ranges::min_element
  • std::ranges::upper_bound

Options

  • ExtraAlgorithms โ€” a semicolon-separated list of extra algorithms to check. The list can contain:
    • Iterator-based algorithms. These should follow the standard iterator pattern: func(Iter, Iter, ...).
  • Range-based algorithms โ€” these get heuristically detected if they take exactly two arguments and the first argument is a container or a range. The fix will insert std::end(Container). Cute, right? ๐ŸŽ€

Default is an empty string.


๐Ÿฆ‹ 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 the fix this check will suggest. ๐Ÿ’…

(Also, for the record: a forwarding reference is an rvalue reference of a type that is a deduced function template argument.)

So in this example, the suggested fix would be

bar(std::forward<T>(t));

Background ๐Ÿ“š

Code like the example above is sometimes written with the expectation that T&& will always end up being an rvalue reference, no matter what type is deduced for T, and that it is therefore not possible to pass an lvalue to foo(). However, this is not true. Consider this example:

std::string s = "Hello, world";
foo(s);

This code compiles and, after the call to foo(), s is left in an indeterminate state because it has been moved from. This may be surprising to the caller of foo() because no std::move was used when calling foo(). ๐Ÿ˜ญ Like, imagine getting robbed and you didn't even hand over your wallet.

The reason for this behavior lies in the special rule for template argument deduction on function templates like foo() โ€” i.e. on function templates that take an rvalue reference argument of a type that is a deduced function template argument. (See section [temp.deduct.call]/3 in the C++11 standard.)

If foo() is called on an lvalue (as in the example above), then T is deduced to be an lvalue reference. In the example, T is deduced to be std::string &. The type of the argument t therefore becomes std::string& &&; by the reference collapsing rules, this collapses to std::string&. โœจ

This means that the foo(s) call passes s as an lvalue reference, and foo() ends up moving s and thereby placing it into an indeterminate state. Yeah. That's the whole vibe. ๐Ÿ’€


๐Ÿ”ฅ 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 undefined behavior bestie

Consider the following example:

void foo(void* ptr);

int main() {
  int x = 42;
  int* ptr = &x;
  int** ptr_ptr = &ptr;
  foo(ptr_ptr); // warning will trigger here
  return 0;
}

In this example, foo() is called with ptr_ptr as its argument. However, ptr_ptr is a int** pointer, while foo() expects a void* pointer. This results in an implicit pointer level conversion, which could cause issues if foo() dereferences the pointer assuming it's a int* pointer. ๐Ÿ™ˆ

Using an explicit cast is a recommended solution to prevent issues caused by implicit pointer level conversion, as it allows the developer to explicitly state their intention and show their reasoning for the type conversion. Additionally, it is recommended that developers thoroughly check and verify the safety of the conversion before using an explicit cast. This extra level of caution can help catch potential issues early on in the development process, improving the overall reliability and maintainability of the code. ๐Ÿ’–

Options ๐ŸŽ€

  • EnableInC โ€” If true, enables the check in C code (it is always enabled in C++ code). Default is true.

๐Ÿ‘‘ bugprone-multiple-new-in-one-expression

Finds multiple new operator calls in a single expression, where the allocated memory by the first new may leak if the second allocation fails and throws exception. ๐Ÿ˜ค

C++ does often not specify the exact order of evaluation of the operands of an operator or arguments of a function. Therefore if a first allocation succeeds and a second fails, in an exception handler it is not possible to tell which allocation has failed and free the memory. Even if the order is fixed the result of a first new may be stored in a temporary location that is not reachable at the time when a second allocation fails. It is best to avoid any expression that contains more than one operator new call, if exception handling is used to check for allocation errors. periodt. ๐Ÿ’…

Different rules apply for are the short-circuit operators || and && and the , operator, where evaluation of one side must be completed before the other starts. Expressions of a list-initialization (initialization or construction using { and } characters) are evaluated in fixed order. Similarly, condition of a ? operator is evaluated before the branches are evaluated. โญ

The check reports warning if two new calls appear in one expression at different sides of an operator, or if new calls appear in different arguments of a function call (that can be an object construction with () syntax). These new calls can be nested at any level. For any warning to be emitted the new calls should be in a code block where exception handling is used with catch for std::bad_alloc or std::exception. At ||, &&, ,, ? (condition and one branch) operators no warning is emitted. No warning is emitted if both of the memory allocations are not assigned to a variable or not passed directly to a function. The reason is that in this case the memory may be intentionally not freed or the allocated objects can be self-destructing objects. ๐Ÿงš

Examples:

struct A {
  int Var;
};
struct B {
  B();
  B(A *);
  int Var;
};
struct C {
  int *X1;
  int *X2;
};

void f(A *, B *);
int f1(A *);
int f1(B *);
bool f2(A *);

void foo() {
  A *PtrA;
  B *PtrB;
  try {
    // Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated.
    f(new A, new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined

    // List (aggregate) initialization is used.
    C C1{new int, new int}; // no warning

    // Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated but not yet passed to function 'f1'.
    int X = f1(new A) + f1(new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined

    // Allocation of 'B' may fail after memory for 'A' was allocated.
    // From C++17 on memory for 'B' is allocated first but still may leak if allocation of 'A' fails.
    PtrB = new B(new A); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception

    // 'new A' and 'new B' may be performed in any order.
    // 'new B'/'new A' may fail after memory for 'A'/'B' was allocated but not assigned to 'PtrA'/'PtrB'.
    (PtrA = new A)->Var = (PtrB = new B)->Var; // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined

    // Evaluation of 'f2(new A)' must be finished before 'f1(new B)' starts.
    // If 'new B' fails the allocated memory for 'A' is supposedly handled correctly because function 'f2' could take the ownership.
    bool Z = f2(new A) || f1(new B); // no warning

    X = (f2(new A) ? f1(new A) : f1(new B)); // no warning

    // No warning if the result of both allocations is not passed to a function
    // or stored in a variable.
    (new A)->Var = (new B)->Var; // no warning

    // No warning if at least one non-throwing allocation is used.
    f(new(std::nothrow) A, new B); // no warning
  } catch(std::bad_alloc) {
  }

  // No warning if the allocation is outside a try block (or no catch handler exists for std::bad_alloc).
  // (The fact if exceptions can escape from 'foo' is not taken into account.)
  f(new A, new B); // no warning
}

๐ŸŒธ bugprone-multiple-statement-macro

Detect multiple statement macros that are used in unbraced conditionals. Only the first statement of the macro will be inside the conditional and the other ones will be executed unconditionally. ๐Ÿซ  like bestie no

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. iykyk ๐Ÿ’…

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; }. ๐Ÿ‘€ sneaky sneaky

We flag narrowing conversions from:

  • an integer to a narrower integer (e.g. char to unsigned char) if WarnOnIntegerNarrowingConversion Option is set,
  • an integer to a narrower floating-point (e.g. uint64_t to float) if WarnOnIntegerToFloatingPointNarrowingConversion Option is set,
  • a floating-point to an integer (e.g. double to int),
  • a floating-point to a narrower floating-point (e.g. double to float) if WarnOnFloatingPointNarrowingConversion Option 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 for narrowing integer conversions (think int โ†’ size_t, the classic ๐Ÿซ ). Defaults to true.
  • WarnOnIntegerToFloatingPointNarrowingConversion โ€” when true, it clocks narrowing conversions from integer to floating-point (like size_t โ†’ double). true by default, bestie.
  • WarnOnFloatingPointNarrowingConversion โ€” when true, it calls out narrowing floating point conversions (yes, double โ†’ float counts ๐Ÿ’€). true by default.
  • WarnWithinTemplateInstantiation โ€” when true, it also snitches on narrowing conversions hiding inside template instantiations. false by default (so templates get a pass unless you say otherwise).
  • WarnOnEquivalentBitWidth โ€” when true, it warns on narrowing conversions that happen when you cast between types of equivalent bit width (like int n = uint(0); or long long n = double(0);). true by default.
  • IgnoreConversionFromTypes โ€” any narrowing conversion from a type in this semicolon-separated list gets straight-up ignored. Super handy for weeding out the stuff that shows up a lot but is rarely actually a problem, like int n = std::vector<char>().size(); or int n = std::difference(it1, it2);. Default list is empty, but if you're staring down a legacy codebase, a suggested list is size_t;ptrdiff_t;size_type;difference_type.
  • PedanticMode โ€” when true, it'll warn on assigning a floating point constant to an integer value even if that floating point value is exactly representable in the destination type (looking at you, int i = 1.0; ๐Ÿ‘€). false by default.

๐ŸŽ€ FAQ ๐ŸŽ€

  • What does "narrowing conversion from 'int' to 'float'" even mean?

Ok so โ€” 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. Slay.

For float that's [-2^23, 2^23], whereas int can represent values in the range [-2^31, 2^31-1]. See the gap? ๐Ÿ’…

  • What does "implementation-defined" mean?

You've probably seen messages like "narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined". So here's the tea: the C/C++ standard does not mandate two's complement for signed integers, which means the compiler is free to decide what the semantics actually are for converting an unsigned integer into a signed one. Clang's implementation uses the two's complement format. No cap.


๐Ÿ’œ bugprone-no-escape

Finds pointers with the noescape attribute that get captured by an asynchronously-executed block. The block arguments in dispatch_async() and dispatch_after() are guaranteed to escape, so it's an error if a pointer with the noescape attribute gets captured by one of these blocks. Iconic of them to catch that, honestly.

Here's an example of the noescape attribute being used all wrong:

       void foo(__attribute__((noescape)) int *p) {
         dispatch_async(queue, ^{
           *p = 123;
         });
       });

๐Ÿฆ‹ bugprone-non-zero-enum-to-bool-conversion

Detects implicit and explicit casts of an enum type into bool where the enum type doesn't have a zero-value enumerator. Think about it โ€” if the enum is only ever used to hold values equal to its enumerators, then converting to bool will always give you true. Which means you've got unnecessary code that tanks readability and maintainability and can legitimately cause bugs. Not the vibe. ๐Ÿ˜ญ

Heads up: it may produce false positives if the enum is used to store other values (like when it's used as 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.

Also important to note: this check will not generate warnings if the definition of the enumeration type isn't available. And yes, C++11 enumeration classes are supported by this check ๐Ÿซถ.

Overall, this check exists to improve code quality and readability by identifying and flagging the instances where implicit or explicit casts from enumeration types to boolean could cause potential issues.

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 to bool. It takes 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 get ignored.

๐ŸŒธ bugprone-nondeterministic-pointer-iteration-order

Finds nondeterministic usages of pointers inside 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 one is sorting a container of pointers:

          {
            int a = 1, b = 2;
            std::vector<int *> VectorOfPtr = {&a, &b};
            std::sort(VectorOfPtr.begin(), VectorOfPtr.end());
          }

Iterating a container of pointers may present the order of different pointers differently across different runs of the same program. Sometimes that's totally acceptable behavior; other times it's the last thing you expected. That's literally why this check is advisory.

Real talk: 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 won't be found โ€” false negatives, bestie, we're owning it ๐Ÿ™ˆ.

Limitations

  • This check currently does not check whether a nondeterministic iteration order is likely to be a mistake โ€” it instead marks all such iterations as bugprone.
  • std::reference_wrapper is not considered yet.
  • Only for loops 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 an equal length to that expression, because the null terminator needs an extra space. Without the null terminator, reading the string can result in undefined behavior. Yeah. That kind of thing. ๐Ÿ’€

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 bump the passed third argument, which is size_t length. That's exactly why the allocated memory isn't big 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;
          }

On top of issuing warnings, the 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 it'll rewrite every path-sensitive memory allocation. Sad but true ๐Ÿฅฒ.

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 copying to the destination array cannot overflow [1], the new function should be the older copy function (ending with cpy), because it's more efficient than the safe version.
  • If copying to the destination array can overflow [1] and WantToUseSafeFunctions is set to true and it's possible to obtain the capacity of the destination array, then the new function could be the safe version (ending with cpy_s).
  • If the new function could be the safe version and C++ files are being analyzed and the destination array is plain char/wchar_t without un/signed, then the length of the destination array can be omitted.
  • If the new function could be the safe version and the destination array is un/signed, it needs to be casted to plain char */wchar_t *.

[1] It is possible to overflow:

  • If the capacity of the destination array is unknown.
  • If the given length is equal to the destination array's capacity.

Rewrite based on the length of the source string

  • If the given length is strlen(source) or equal length to that expression, then the new function should be the older copy function (ending with cpy), since it's more efficient than the safe version (ending with cpy_s).
  • Otherwise we assume 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 doesn't have a wchar_t based alias).

Memory handler functions

memcpy โ€” please visit the Transformation rules of 'memcpy()' section.

memchr โ€” usually there's a C-style cast and it needs to be removed, because the new function strchr's return type is already correct. The given length is going to be removed.

  • memmove โ€” if safe functions are available, the new function is memmove_s, which has a new second argument that's the length of the destination array, it gets adjusted, and the length of the source string gets incremented by one. If safe functions aren't available, the given length just gets 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. lowkey a whole little rule, but ok.
  • strxfrm โ€” the given length is incremented by one.

โš™๏ธ Options

  • WantToUseSafeFunctions โ€” the value true means the target environment is considered to implement '_s' suffixed memory and string handler functions that are safer than the older versions (e.g. 'memcpy_s()'). The default value is true. slay, safety first ๐Ÿ’…

bugprone-optional-value-conversion

Okay bestie, this one detects potentially unintentional and redundant conversions where a value gets extracted from an optional-like type and then used to create a brand new instance of the very same optional-like type. ๐Ÿ”

These conversions might be leftovers from a refactor, plain old developer oversight, or other situations that could lead to unintended exceptions โ€” or to cases where the resulting optional is always initialized, which might be unexpected behavior. ๐Ÿซ 

To illustrate, consider this problematic little 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 just pass opt directly 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 print, unnecessary conversions are avoided and potential unintended behavior or exceptions are minimized. iconic behavior honestly โœจ

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. like, you skipped a generation and the compiler is ๐Ÿ‘€

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. scary fr fr ๐Ÿ’€

Finding pointers where the static type contains a virtual member function is a good heuristic, since 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 โ€” when true, objects that only inherit a virtual function are not checked. Classes that do not declare a new virtual function are excluded by default, since 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 like, < 0 is literally never happening bestie ๐Ÿ˜ญ

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-random-generator-seed

Flags all pseudo-random number engines, engine adaptor instantiations and srand() when initialized or seeded with a default argument, a constant expression, or any user-configurable type. Pseudo-random number engines seeded with a predictable value may cause vulnerabilities, e.g. in security protocols. ๐Ÿ” not the vibe!!

Examples:

void foo() {
  std::mt19937 engine1; // Diagnose, always generate the same sequence
  std::mt19937 engine2(1); // Diagnose
  engine1.seed(); // Diagnose
  engine2.seed(1); // Diagnose

  std::time_t t;
  engine1.seed(std::time(&t)); // Diagnose, system time might be controlled by user

  int x = atoi(argv[1]);
  std::mt19937 engine3(x);  // Will not warn
}

โš™๏ธ Options

  • DisallowedSeedTypes โ€” a comma-separated list of the type names which are disallowed. Default value is time_t,std::time_t.

๐Ÿ“š References

This check corresponds to the CERT C++ Coding Standard rules MSC51-CPP. Ensure your random number generator is properly seeded and MSC32-C. Properly seed pseudorandom number generators. ๐ŸŒˆ


bugprone-raw-memory-call-on-non-trivial-type

Flags use of the C standard library functions memset, memcpy and memcmp and similar derivatives on non-trivial types. ๐Ÿซ 

The check will detect the following functions: memset, std::memset, std::memcpy, memcpy, std::memmove, memmove, std::strcpy, strcpy, memccpy, stpncpy, strncpy, std::memcmp, memcmp, std::strcmp, strcmp, strncmp.

โš™๏ธ Options

  • MemSetNames โ€” specify extra functions to flag that act similarly to memset. Specify names in a semicolon-delimited list. Default is an empty string.
  • MemCpyNames โ€” specify extra functions to flag that act similarly to memcpy. Specify names in a semicolon-delimited list. Default is an empty string.
  • MemCmpNames โ€” specify extra functions to flag that act similarly to memcmp. Specify names in a semicolon-delimited list. Default is an empty string.

This check corresponds to the CERT C++ Coding Standard rule OOP57-CPP. Prefer special member functions and overloaded operators to C Standard Library functions. ๐Ÿ‘‘


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 bestie we already checked that ๐Ÿ’€

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, if it is volatile, or if it has an alias (pointer or reference), then no warning is issued. ๐Ÿซถ

๐Ÿšง 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.

And bestie, it gets worse โ€” the C standard also reserves names beginning with a double underscore, and C++ strengthens that whole vibe to reserve names with a double underscore occurring anywhere. ๐Ÿ™ˆ

Violating the naming rules above results in undefined behavior. Not "oops a warning" behavior. Undefined. Behavior. ๐Ÿ’€

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 โ€” like, configured to flag any identifier that is not a reserved identifier. That mode is for use by e.g. standard library implementors, so they can be sure they're not infringing on the user namespace. Respect the boundaries, kings. ๐Ÿ‘‘

Also pls note: this check does not (yet) check for other reserved names โ€” like macro names identical to language keywords, or names specifically reserved by language standards, e.g. C++ 'zombie names' and C future library directions. It's giving "we'll get there when we get there." ๐Ÿงš

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 โ€” If true, inverts the check, i.e. flags names that are not reserved. Default is false.
  • 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. Which lowkey can 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. So 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. Rude, honestly.

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. And 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. Slay. ๐Ÿ’…

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

Finds initializations of C++ shared pointers to non-array type that are initialized with an array. The audacity. ๐Ÿ˜ค

So here's the tea: 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. But a delete[] call is needed. A std::shared_ptr<T[]> calls the correct delete operator. Periodt.

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:

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 โ€” and this is the fine print, bestie โ€” 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โ€ฆ check your version first. ๐Ÿซถ

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. Not me finding that out the hard way ๐Ÿ˜ญ

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 functions abort(), _Exit(), quick_exit() and signal().
    • 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

Fun fact: the function quick_exit is not included in the POSIX list but it is included here in the set of safe functions. Special guest star behavior โœจ

The default value is POSIX.


๐Ÿ’œ bugprone-signed-bitwise

Finds uses of bitwise operations on signed integer types, which may lead to undefined or implementation defined behavior. ๐Ÿšฉ

Performing bitwise operations on signed integers can be confusing and may lead to undefined or implementation dependent behavior. In particular, right shift a signed integer is implementation dependent, while left shift a signed integer may result in undefined behavior.

int main(){
   int x = -4;
   int y = x >> 1; // y can be -2 or 2147483646
}

โœจ Options โœจ

  • IgnorePositiveIntegerLiterals โ€” If this option is set to true, the check will not warn on bitwise operations with positive integer literals, e.g. ~0, 2 << 1, etc. Default value is false.

๐ŸŒˆ 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. iykyk ๐Ÿ’ซ

Finds those signed char -> integer conversions which might indicate a programming error. The basic problem with the signed char is 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. And that's the whole vibe check fail โ€” you wrote something, but it does not mean what you think it means. ๐Ÿ˜ญ๐Ÿซถ

okay so when your code has an explicit signed char -> integer conversion, the human who wrote it is probably crossing their fingers that the converted value matches the character code โ€” you know, the whole [0..255] vibe โ€” but plot twist: the actual value lands in the [-128..127] interval. ๐Ÿ’€ To dodge that kind of misinterpretation, the desired way of converting from a signed char to an integer value is converting to unsigned char first, because that stores all the characters in the positive [0..255] interval, which matches the known character codes. slay.

Now for implicit conversion: the programmer might not actually be aware that a conversion happened and that a char value is being used as an integer. There are some use cases when this unawareness might lead to functionally imperfect code. For example, checking 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 the [-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 is when a char variable is used to read from a file that might contain non-ASCII characters. The problem comes up when the code uses the -1 integer value as EOF, while the 255 character code is also stored as -1 in two's complement form of char type. See a simple example of this below. This code stops not only when it reaches the end of the file, but also when it gets a character with the 255 code.

#define EOF (-1)

int read(void) {
  char CChar;
  int IChar = EOF;

  if (readChar(CChar)) {
    IChar = CChar;
  }
  return IChar;
}

A proper way to fix the code above is converting the char variable to an unsigned char value first. ๐Ÿซถ

#define EOF (-1)

int read(void) {
  char CChar;
  int IChar = EOF;

  if (readChar(CChar)) {
    IChar = static_cast<unsigned char>(CChar);
  }
  return IChar;
}

Another use case is checking the equality of two char variables with different signedness. Inside the non-ASCII value range, this comparison between a signed char and an unsigned char always returns false.

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;
}

โœจ Options โœจ

  • CharTypedefsToIgnore โ€” A semicolon-separated list of typedef names. In this list, we can list typedefs for char or signed char, which will be ignored by the check. This is useful when a typedef introduces an integer alias like sal_Int8 or int8_t. In this case, human misinterpretation is not an issue. Default is an empty string.
  • DiagnoseSignedUnsignedCharComparisons โ€” When true, the check will warn on signed char/unsigned char comparisons, otherwise these comparisons are ignored. By default, this option is set to true.

๐ŸŽ€ bugprone-sizeof-container ๐ŸŽ€

The check finds usages of sizeof on expressions of STL container types. Most likely the user wanted to use .size() instead.

All class/struct types declared in namespace std:: having a const size() method are considered containers, with the exception of std::bitset and std::array.

Examples:

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 ๐Ÿฆ‹

The check finds usages of sizeof expressions which are most likely errors.

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. ๐Ÿ˜ญ

๐Ÿ” Suspicious usage of 'sizeof(K)' ๐Ÿ”

A common mistake is to query the sizeof of an integer literal. This is equivalent to query the size of its type (probably int). The intent of the programmer was probably to simply get the integer and not its size.

#define BUFLEN 42
char buf[BUFLEN];
memset(buf, 0, sizeof(BUFLEN));  // sizeof(42) ==> sizeof(int)

๐Ÿ” Suspicious usage of 'sizeof(expr)' ๐Ÿ”

In cases where there is an enum or integer to represent a type, a common mistake is to query the sizeof on the integer or enum that represents the type that should be used by sizeof. This results in the size of the integer and not of the type the integer represents:

enum data_type {
  FLOAT_TYPE,
  DOUBLE_TYPE
};

struct data {
  data_type type;
  void* buffer;
  data_type get_type() {
    return type;
  }
};

void f(data d, int numElements) {
  // should be sizeof(float) or sizeof(double), depending on d.get_type()
  int numBytes = numElements * sizeof(d.get_type());
  ...
}

๐Ÿ” Suspicious usage of 'sizeof(this)' ๐Ÿ”

The this keyword is evaluated to a pointer to an object of a given type. The expression sizeof(this) is returning the size of a pointer. The programmer most likely wanted the size of the object and not the size of the pointer.

class Point {
  [...]
  size_t size() { return sizeof(this); }  // should probably be sizeof(*this)
  [...]
};

๐Ÿ” Suspicious usage of 'sizeof(char*)' ๐Ÿ”

There is a subtle difference between declaring a string literal with char* A = "" and char A[] = "". The first case has the type char* instead of the aggregate type char[]. Using sizeof on an object declared with char* type is returning the size of a pointer instead of the number of characters (bytes) in the string literal.

const char* kMessage = "Hello World!";      // const char kMessage[] = "...";
void getMessage(char* buf) {
  memcpy(buf, kMessage, sizeof(kMessage));  // sizeof(char*)
}

๐Ÿ” Suspicious usage of 'sizeof(A*)' ๐Ÿ”

A common mistake is to compute the size of a pointer instead of its pointee. These cases may occur because of explicit cast or implicit conversion.

int A[10];
memset(A, 0, sizeof(A + 0));

struct Point point;
memset(point, 0, sizeof(&point));

๐Ÿ” Suspicious usage of 'sizeof(...)/sizeof(...)' ๐Ÿ”

Dividing sizeof expressions is typically used to retrieve the number of elements of an aggregate. This check warns on incompatible or suspicious cases.

In the following example, the entity has 10-bytes and is incompatible with the type int which has 4 bytes.

char buf[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };  // sizeof(buf) => 10
void getMessage(char* dst) {
  memcpy(dst, buf, sizeof(buf) / sizeof(int));  // sizeof(int) => 4  [incompatible sizes]
}

In the following example, the expression sizeof(Values) is returning the size of char*. One can easily be fooled by its declaration, but in parameter declaration the size 10 is ignored and the function is receiving a char*.

char OrderedValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return CompareArray(char Values[10]) {
  return memcmp(OrderedValues, Values, sizeof(Values)) == 0;  // sizeof(Values) ==> sizeof(char*) [implicit cast to char*]
}

๐Ÿ” Suspicious 'sizeof' by 'sizeof' expression ๐Ÿ”

Multiplying sizeof expressions typically makes no sense and is probably a logic error. In the following example, the programmer used * instead of /.

const char kMessage[] = "Hello World!";
void getMessage(char* buf) {
  memcpy(buf, kMessage, sizeof(kMessage) * sizeof(char));  //  sizeof(kMessage) / sizeof(char)
}

This check may trigger on code using the arraysize macro. The following code is working correctly but should be simplified by using only the sizeof operator.

extern Object objects[100];
void InitializeObjects() {
  memset(objects, 0, arraysize(objects) * sizeof(Object));  // sizeof(objects)
}

๐Ÿ” Suspicious usage of 'sizeof(sizeof(...))' ๐Ÿ”

Getting the sizeof of a sizeof makes no sense and is typically an error hidden through macros.

#define INT_SZ sizeof(int)
int buf[] = { 42 };
void getInt(int* dst) {
  memcpy(dst, buf, sizeof(INT_SZ));  // sizeof(sizeof(int)) is suspicious.
}

๐Ÿ” Suspicious usages of 'sizeof(...)' in pointer arithmetic ๐Ÿ”

Arithmetic operators on pointers automatically scale the result with the size of the pointed typed. Further use of sizeof around pointer arithmetic will typically result in an unintended result.

๐Ÿ” 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 ๐Ÿ’ซ

Okay so flip the vibe for a sec โ€” when you do pointer arithmetic to add or subtract from a pointer, the arithmetic operator implicitly scales the value you actually add by the size of the pointee. Because Ptr + N expects N to mean "number of objects to step", not "number of bytes to step". Yes bestie, the math is doing a little secret multiplication behind your back.

So seeing a sizeof show up inside a pointer calculation is mad suspicious, and the result is typically not what you wanted, often straight-up out of bounds. Ptr + sizeof(T) will offset the pointer by sizeof(T) elements, which effectively exponentiates the scaling factor to the power of 2. It's giving "doubled the double" ๐Ÿ˜ญ.

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. iykyk ๐Ÿซถ

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. Sneaky.

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. Cute little alias moment ๐Ÿ’…

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. Not invited to the party, sorry char.

๐ŸŽ€ Options ๐ŸŽ€

  • WarnOnSizeOfConstant โ€” When true, the check will warn on an expression like sizeof(CONSTANT). Default is true.
  • WarnOnSizeOfIntegerExpression โ€” When true, the check will warn on an expression like sizeof(expr) where the expression results in an integer. Default is false.
  • WarnOnSizeOfThis โ€” When true, the check will warn on an expression like sizeof(this). Default is true.
  • WarnOnSizeOfCompareToConstant โ€” When true, the check will warn on an expression like sizeof(expr) <= k for a suspicious constant k while k is 0 or greater than 0x8000. Default is true.
  • WarnOnSizeOfPointerToAggregate โ€” When true, the check will warn when the argument of sizeof is 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 example array + 2 or (int *)array). Default is true.
  • WarnOnSizeOfPointer โ€” When true, the check will report all expressions where the argument of sizeof is 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 is false.
  • WarnOnOffsetDividedBySizeOf โ€” When true, the check will warn on pointer arithmetic where the element count is obtained from a division with sizeof(...), e.g., Ptr + Bytes / sizeof(*T). Default is true.
  • WarnOnSizeOfInLoopTermination โ€” When true, the check will warn about incorrect use of sizeof expression in loop termination condition. The warning triggers if the sizeof expression appears to be incorrectly used to determine the number of array/buffer elements. e.g, long arr[10]; for(int i = 0; i < sizeof(arr); i++) { ... }. Default is true.

๐Ÿฆ‹ bugprone-spuriously-wake-up-functions ๐Ÿฆ‹

Finds cnd_wait, cnd_timedwait, wait, wait_for, or wait_until function calls when the function is not invoked from a loop that checks whether a condition predicate holds, or the function has a condition parameter. Waking up for no reason? Not on this check's watch, bestie ๐Ÿ˜ค

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 CERT C Coding Standard rule CON36-C. Wrap functions that can spuriously wake up in a loop.


โœจ bugprone-standalone-empty โœจ

Warns when empty() is used on a range and the result is ignored. Suggests clear() if it is an existing member function. Iconic little mistake, honestly.

The empty() method on several common ranges returns a Boolean indicating whether or not the range is empty, but is often mistakenly interpreted as a way to clear the contents of a range. Some ranges offer a clear() method for this purpose. This check warns when a call to empty returns a result that is ignored, and suggests replacing it with a call to clear() if it is available as a member function of the range.

For example, the following code could be used to indicate whether a range is empty or not, but the result is ignored:

std::vector<int> v;
...
v.empty();

A call to clear() would appropriately clear the contents of the range:

std::vector<int> v;
...
v.clear();

๐ŸŒธ Limitations ๐ŸŒธ

  • Doesn't warn if empty() is defined and used with the ignore result in the class template definition (for example in the library implementation). These error cases can be caught with [[nodiscard]] attribute. Consider this your friendly nudge ๐Ÿ‘€

๐Ÿ’œ bugprone-std-exception-baseclass ๐Ÿ’œ

Ensure that every value that in a throw expression is an instance of std::exception.

Deriving all exceptions from std::exception allows callers to catch all exceptions with a single catch block and provides access to the what() method for diagnostics. Throwing arbitrary types creates hidden contracts, reduces interoperability with the standard library, and may result in program termination. Yeahโ€ฆ termination is not the slay we're going for ๐Ÿ’€

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();
}

๐ŸŽ€ bugprone-std-namespace-modification ๐ŸŽ€

Warns on modifications of the std or posix namespaces which can result in undefined behavior. Bestie, do not touch, that's their house ๐Ÿ 

The std (or posix) namespace is allowed to be extended with (class or function) template specializations that depend on an user-defined type (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:

namespace std {
  int x; // warning: modification of 'std' namespace can result in undefined behavior [bugprone-dont-modify-std-namespace]
}

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;
  }
}

โญ References โญ

This check corresponds to the CERT C++ Coding Standard rule DCL58-CPP. Do not modify the standard namespaces.


๐Ÿ“ bugprone-string-constructor ๐Ÿ“

Finds string constructors that are suspicious and probably errors. Like, the constructor said "trust me" and you should not have trusted it ๐Ÿ™ˆ

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. Random characters?? In my string?? It's more likely than you think ๐Ÿ˜ญ

Examples:

std::string("test", 200);   // Will include random characters after "test".
std::string("test", 2, 5);  // Will include random characters after "st".
std::string_view("test", 200);

Creating an empty string from constructors with parameters is considered suspicious. The programmer should use the empty constructor instead. The empty constructor ate and left no crumbs, periodt.

Examples:

std::string("test", 0);   // Creation of an empty string.
std::string("test", 1, 0);
std::string_view("test", 0);

Passing an invalid first character position parameter to constructor will cause std::out_of_range exception at runtime. Not a fun surprise ๐Ÿซ 

Examples:

std::string("test", -1, 10); // Negative first character position.
std::string("test", 10, 10); // First character position is bigger than string literal character range".

๐ŸŒˆ Options ๐ŸŒˆ

  • WarnOnLargeLength โ€” When true, the check will warn on a string with a length greater than LargeLengthThreshold. Default is true.
  • LargeLengthThreshold โ€” An integer specifying the large length threshold. Default is 0x800000.
  • StringNames โ€” Default is ::std::basic_string;::std::basic_string_view.

Okay so this option is a 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

The 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.

std::string s;
int x = 5965;
s = 6;
s = x;

Use the appropriate conversion functions or character literals.

std::string s;
int x = 5965;
s = '6';
s = std::to_string(x);

In order to suppress false positives, use an explicit cast.

std::string s;
s = static_cast<char>(6);

๐Ÿงš bugprone-string-literal-with-embedded-nul

Finds occurrences of string literal with embedded NUL character and validates their usage.

Invalid escaping

Special characters can be escaped within a string literal by using their hexadecimal encoding like \x42. A common mistake is to escape them like this \0x42 where the \0 stands for the NUL character.

const char* Example[] = "Invalid character: \0x12 should be \x12";
const char* Bytes[] = "\x03\0x02\0x01\0x00\0xFF\0xFF\0xFF";

Truncated literal

String-like classes can manipulate strings with embedded NUL as they are keeping track of the bytes and the length. This is not the case for a char* (NUL-terminated) string.

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.

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 the nullptr literal, 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 is 0. 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).

  1. 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.

  1. Investigating the right hand side of += and |= operator.
  2. Check only the enum value side of a | and + operator if one of them is not enum val.
  3. Check both side of | or + operator where the enum values are from the same enum type.

Examples:

enum { A, B, C };
enum { D, E, F = 5 };
enum { G = 10, H = 11, I = 12 };

unsigned flag;
flag =
    A |
    H; // OK, disjoint value intervals in the enum types ->probably good use.
flag = B | F; // Warning, have common values so they are probably misused.

// Case 2:
enum Bitmask {
  A = 0,
  B = 1,
  C = 2,
  D = 4,
  E = 8,
  F = 16,
  G = 31 // OK, real bitmask.
};

enum Almostbitmask {
  AA = 0,
  BB = 1,
  CC = 2,
  DD = 4,
  EE = 8,
  FF = 16,
  GG // Problem, forgot to initialize.
};

unsigned flag = 0;
flag |= E; // OK.
flag |=
    EE; // Warning at the decl, and note that it was used here as a bitmask.

Options

  • StrictMode โ€” Default value: 0. When non-null the suspicious bitmask usage will be investigated additionally to the different enum usage check.

๐Ÿ’œ bugprone-suspicious-include

The check detects various cases when an include refers to what appears to be an implementation file, which often leads to hard-to-track-down ODR violations.

Examples:

#include "Dinosaur.hpp"     // OK, .hpp files tend not to have definitions.
#include "Pterodactyl.h"    // OK, .h files tend not to have definitions.
#include "Velociraptor.cpp" // Warning, filename is suspicious.
#include_next <stdio.c>     // Warning, filename is suspicious.

Options

  • IgnoredRegex โ€” A regular expression for the file name to be ignored by the check. Default is empty string.

๐ŸŒธ 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, possibly integer zeroes were intended instead. The check offers a replacement of '0' with 0. Memsetting character pointers with '0' is allowed.

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.

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.

Corresponding cpplint.py check name: runtime/memset.

Examples:

void foo() {
  int i[5] = {1, 2, 3, 4, 5};
  int *ip = i;
  char c = '1';
  char *cp = &c;
  int v = 0;

  // Case 1
  memset(ip, '0', 1); // suspicious
  memset(cp, '0', 1); // OK

  // Case 2
  memset(ip, 0xabcd, 1); // fill value gets truncated
  memset(ip, 0x00, 1);   // OK

  // Case 3
  memset(ip, sizeof(int), v); // zero length, potentially swapped
  memset(ip, 0, 1);           // OK
}

๐Ÿ”ฅ bugprone-suspicious-missing-comma

String literals placed side-by-side are concatenated at translation phase 6 (after the preprocessor). This feature is used to represent long string literal on multiple lines.

For instance, the following declarations are equivalent:

const char* A[] = "This is a test";
const char* B[] = "This" " is a "    "test";

A common mistake done by programmers is to forget a comma between two string literals in an array initializer list.

const char* Test[] = {
  "line 1",
  "line 2"     // Missing comma!
  "line 3",
  "line 4",
  "line 5"
};

The array contains the string "line 2line3" at offset 1 (i.e. Test[1]). Clang wonโ€™t generate warnings at compile time.

This check may warn incorrectly on cases like:

const char* SupportedFormat[] = {
  "Error %s",
  "Code " PRIu64,   // May warn here.
  "Warning %s",
};

Options

  • SizeThreshold โ€” An unsigned integer specifying the minimum size of a string literal to be considered by the check. Default is 5U.
  • RatioThreshold โ€” A string specifying the maximum threshold ratio [0, 1.0] of suspicious string literals to be considered. Default is ".2".
  • MaxConcatenatedTokens โ€” An unsigned integer specifying the maximum number of concatenated tokens. Default is 5U.

๐ŸŒ€ bugprone-suspicious-realloc-usage

okay bestie, this one is lowkey a lifesaver. It finds places where you use realloc and then assign the return value to the same expression you handed to the first argument, like this:

p = realloc(p, size);

and the whole problem here is ๐Ÿ’€ that if realloc fails, it returns a null pointer โ€” but it does not deallocate the original memory. So if no other variable is pointing at that original block, it's just... gone. Not available for the program to use, not available to free. Poof. โœจgoneโœจ

Either way, p = realloc(p, size); is giving bad coding style, and you can swap it for:

q = realloc(p, size);

The pointer expression (the thing you used at realloc) can be a variable or a field member of a data structure โ€” but it can not contain function calls or unresolved types. ๐Ÿšซ

Now, in obvious cases where the pointer used at realloc gets assigned to another variable before the realloc call, no warning is emitted. This only happens if a simple expression in the form of q = p or void *q = p is found in the same function where p = realloc(p, ...) lives. The assignment has to come before the call to realloc (but otherwise it can be at any place) in that same function. This suppression only works if p is a single variable. ๐ŸŽ€

Examples, because visuals slay:

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

the way this one hunts down stray semicolons that โœจunexpectedly change what your code meansโœจ โ€” iconic detective energy honestly. More specifically: it looks for if, while, for and for-range statements whose body is a single semicolon, and then it analyzes the context of the code (indentation, for example) trying to figure out whether you meant to do that. ๐Ÿ”

if (x < y);
{
  x++;
}

so here the body of the if statement is just the semicolon at the end of the first line, and x gets incremented regardless of the condition. rude.

while ((line = readLine(file)) != NULL);
  processLine(line);

as a result of this, processLine() will only be called once, when the while loop with the empty body exits with line == NULL. And the indentation here is literally telling on you โ€” it shows the programmer's intention.

if (x >= y);
x -= y;

here the indentation doesn't imply any nesting at all, and there's just no valid reason to have an if statement with an empty body (it can make sense for a loop, tho). So this check issues a warning for the code above. โš ๏ธ

To fix it: remove the stray semicolon โ€” or, if the empty body is genuinely intentional, reflect that with code indentation or put the semicolon on a new line. Like:

while (readWhitespace());
  Token t = readNextToken();

here the second line is indented in a way that suggests it's meant to be the body of the while loop โ€” whose body is, in fact, empty, because of that 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 assumes you know what you're doing and won't raise a warning. ๐Ÿซถ


๐ŸŒ€ bugprone-suspicious-string-compare

finds suspicious usage of runtime string comparison functions โ€” and yes, this check is valid in both C and C++. ๐ŸŒˆ

It checks for calls with an implicit comparator and proposes you explicitly add one:

if (strcmp(...))       // Implicitly compare to zero
if (!strcmp(...))      // Won't warn
if (strcmp(...) != 0)  // Won't warn

It also checks that comparison function results (i.e., strcmp) are compared against a valid constant. The resulting value is:

<  0    when lower than,
>  0    when greater than,
== 0    when equals.

A common mistake is comparing the result to 1 or -1. ๐Ÿ’€

if (strcmp(...) == -1)  // Incorrect usage of the returned value.

On top of that, the check warns if the result value gets implicitly cast to a suspicious non-integer type. That's what's happening when the returned value gets used in the wrong context:

if (strcmp(...) < 0.)  // Incorrect usage of the returned value.

โœจ Options

  • WarnOnImplicitComparison โ€” when true, the check warns on implicit comparison. true by default.
  • WarnOnLogicalNotComparison โ€” when true, the check warns on logical not comparison. false by default.
  • StringCompareLikeFunctions โ€” a string specifying the comma-separated names of 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

this one identifies suspicious usages of std::string_view::data() that could lead to reading out-of-bounds data because of inadequate or incorrect string null termination. ๐Ÿ˜ฌ

It warns when the result of data() gets passed to a constructor or function without also passing the corresponding result of the size() or length() member function. That kind of usage can lead to unintended behavior โ€” especially when you're 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. But since data() does not guarantee null termination, this can cause unintended behavior if the API relies on proper null termination for correct string interpretation. fr fr, it's a trap. ๐Ÿชค

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, trying to pass an std::string_view to a function expecting an std::string results in a compilation error. As a workaround, developers get tempted to use the .data() method to make it compile โ€” 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 because of 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. ๐Ÿ’…

โœจ Options

  • 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 you 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 potentially swapped arguments by examining implicit conversions. ๐Ÿ‘€ It analyzes the types of the arguments being passed to a function and compares them to the expected types of the corresponding parameters. If there's a mismatch or an implicit conversion that indicates a potential swap โ€” 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 wide range of implicit conversions, including:

  • User-defined conversions
  • Conversions from floating-point types to boolean or integral types
  • Conversions from integral types to boolean or floating-point types
  • Conversions from boolean to integer types or floating-point types
  • Conversions from (member) pointers to boolean

it's important to note that for most argument swaps, the types need to match exactly. But there are exceptions to this rule. Specifically, when the swapped argument is of integral type, an exact match isn't always necessary โ€” implicit casts from other integral types are also accepted. Similarly, when dealing with floating-point arguments, implicit casts between different floating-point types are considered acceptable. ๐ŸŽ€

And to avoid confusion: swaps where both swapped arguments are of integral types, or both are of floating-point types, do not trigger the warning. In those cases it's assumed that the developer intentionally used different integral or floating-point types, so no warning is raised. This approach prevents false positives and gives you flexibility in handling situations where varying integral or floating-point types are intentionally used. ๐Ÿซถ


๐ŸŒ€ bugprone-switch-missing-default-case

ensures that switch statements without default cases get flagged โ€” and it focuses only on covering cases with non-enums, where the compiler may not issue warnings. ๐ŸŽฏ

Switch statements without a default case can lead to unexpected behavior and incomplete handling of all possible cases. When a switch statement lacks a default case, and a value shows up that doesn't match any of the specified cases, the switch just... does nothing. ๐Ÿ˜ถ And the program continues executing without handling that value.

This check helps identify switch statements missing a default case, so developers can make sure all possible cases get 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

Okay so real talk: enum types are already covered by compiler warnings (comes under -Wswitch) when a switch statement does not handle all enum values. This check focuses on non-enum types where the compiler warnings may not be present. ๐Ÿซถ

๐Ÿ”Ž SEE ALSO

The CppCoreGuideline ES.79 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. ๐Ÿ’…

A struct or a class is considered to be a tagged union if it has exactly one union data member and exactly one enum data member and any number of other data members that are neither unions or enums. Furthermore, the types of the union and the enum members must not come from system header files nor the std namespace. ๐Ÿ‘‘

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;
};

The following example illustrates the exception for unions and enums from system header files and the std namespace. ๐Ÿฆ‹

#include <pthread.h>

struct NotTaggedUnion {
  enum MyEnum { MyEnumConstant1, MyEnumConstant2 } En;
  pthread_mutex_t Mutex;
};

The pthread_mutex_t type may be defined as a union behind a typedef, in which case the check could mistake this type as a user-defined tagged union. After all, it has exactly one enum data member and exactly one union data member. To avoid false-positive cases originating from this, unions and enums from system headers and the std namespace are ignored when pinpointing the union part and the enum part of a potential user-defined tagged union. ๐ŸŽ€

๐Ÿงฎ 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 are not included in the final count. โญ

Handling of counting enum constants (ones like TagCount in the previous code example) is done by decreasing the number of enum values by one if the name of the last enum constant starts with a prefix or ends with a suffix specified in CountingEnumPrefixes, CountingEnumSuffixes and its value is one less than the total number of distinct values in the enum. ๐Ÿงš

When the final count is adjusted based on this heuristic then a diagnostic note is emitted that shows which enum constant matched the criteria. ๐Ÿ‘‘

The heuristic can be disabled entirely (EnableCountingEnumHeuristic) or configured to follow your naming convention (CountingEnumPrefixes, CountingEnumSuffixes). The strings specified in CountingEnumPrefixes, CountingEnumSuffixes are matched case insensitively. ๐ŸŒธ

Example counts:

// Enum count is 3, because the value 2 is counted only once
enum TagWithLast {
  Tag1 = 0,
  Tag2 = 1,
  Tag3 = 2,
  LastTag = 2
};

// Enum count is 3, because TagCount is heuristically excluded
enum TagWithCounter {
  Tag1, // is 0
  Tag2, // is 1
  Tag3, // is 2
  TagCount, // is 3
};

โš™๏ธ Options

EnableCountingEnumHeuristic

This option enables or disables the counting enum heuristic. It uses the prefixes and suffixes specified in the options CountingEnumPrefixes, CountingEnumSuffixes to find counting enum constants by using them for prefix and suffix matching. โœจ

This option is enabled by default.

When EnableCountingEnumHeuristic is false:

enum TagWithCounter {
  Tag1,
  Tag2,
  Tag3,
  TagCount,
};

struct TaggedUnion {
  TagWithCounter Kind;
  union {
    int A;
    long B;
    char *Str;
    float F;
  } Data;
};

When EnableCountingEnumHeuristic is true:

enum TagWithCounter {
  Tag1,
  Tag2,
  Tag3,
  TagCount,
};

struct TaggedUnion { // warning: tagged union has more data members (4) than tags (3)
  TagWithCounter Kind;
  union {
    int A;
    long B;
    char *Str;
    float F;
  } Data;
};

CountingEnumPrefixes

See CountingEnumSuffixes below. ๐Ÿ‘€

CountingEnumSuffixes

CountingEnumPrefixes and CountingEnumSuffixes are lists of semicolon separated strings that are used to search for possible counting enum constants. These strings are matched case insensitively as prefixes and suffixes respectively on the names of the enum constants. If EnableCountingEnumHeuristic is false then these options do nothing. ๐Ÿ’…

The default value of CountingEnumSuffixes is count and of CountingEnumPrefixes is the empty string.

When EnableCountingEnumHeuristic is true and CountingEnumSuffixes is count;size:

enum TagWithCounterCount {
  Tag1,
  Tag2,
  Tag3,
  TagCount,
};

struct TaggedUnionCount { // warning: tagged union has more data members (4) than tags (3)
  TagWithCounterCount Kind;
  union {
    int A;
    long B;
    char *Str;
    float F;
  } Data;
};

enum TagWithCounterSize {
  Tag11,
  Tag22,
  Tag33,
  TagSize,
};

struct TaggedUnionSize { // warning: tagged union has more data members (4) than tags (3)
  TagWithCounterSize Kind;
  union {
    int A;
    long B;
    char *Str;
    float F;
  } Data;
};

When EnableCountingEnumHeuristic is true and CountingEnumPrefixes is maxsize;last_

enum TagWithCounterLast {
  Tag1,
  Tag2,
  Tag3,
  last_tag,
};

struct TaggedUnionLast { // warning: tagged union has more data members (4) than tags (3)
  TagWithCounterLast tag;
  union {
    int I;
    short S;
    char *C;
    float F;
  } Data;
};

enum TagWithCounterMaxSize {
  Tag1,
  Tag2,
  Tag3,
  MaxSizeTag,
};

struct TaggedUnionMaxSize { // warning: tagged union has more data members (4) than tags (3)
  TagWithCounterMaxSize tag;
  union {
    int I;
    short S;
    char *C;
    float F;
  } Data;
};

StrictMode

When enabled, the check will also give a warning, when the number of tags is greater than the number of union data members. ๐Ÿ”ฅ

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

Detects do while loops with a condition always evaluating to false that have a continue statement, as this continue terminates the loop effectively. ๐Ÿ’€

void f() {
do {
  // some code
  continue; // terminating continue
  // some other code
} while(false);

๐ŸŽฏ bugprone-throw-keyword-missing

Warns about a potentially missing throw keyword. If a temporary object is created, but the object's type derives from (or is the same as) a class that has 'EXCEPTION', 'Exception' or 'exception' in its name, we can assume that the programmer's intention was to throw that object. ๐Ÿซถ

Example:

void f(int i) {
  if (i < 0) {
    // Exception is created but is not thrown.
    std::runtime_error("Unexpected argument");
  }
}

๐Ÿ’ฅ bugprone-throwing-static-initialization

Finds all static or thread_local variable declarations where the initializer for the object may throw an exception. ๐Ÿ˜ญ

โš™๏ธ Options

AllowedTypes

A semicolon-separated list of names of types that will be excluded from this check (declarations with matching type will be excluded). Regular expressions are accepted, e.g. [Rr]ef(erence)?$ matches every type with suffix Ref, ref, Reference and reference. If a name in the list contains the sequence ::, it is matched against the qualified type name (i.e. namespace::Type), otherwise it is matched against only the type name (i.e. Type). Default is an empty string. ๐ŸŒˆ

๐Ÿ“š References

This check corresponds to the CERT C++ Coding Standard rule ERR58-CPP. Handle all exceptions thrown before main() begins executing. ๐Ÿ‘‘


๐Ÿœ bugprone-too-small-loop-variable

Detects those for loops that have a loop variable with a "too small" type which means this type can't represent all values which are part of the iteration range. ๐Ÿฆ‹

int main() {
  long size = 294967296l;
  for (short i = 0; i < size; ++i) {}
}

This for loop is an infinite loop because the short type can't represent all values in the [0..size] interval. ๐Ÿ’€

In a real use case size means a container's size which depends on the user input.

int doSomething(const std::vector& items) {
  for (short i = 0; i < items.size(); ++i) {}
}

This algorithm works for a small amount of objects, but will lead to freeze for a larger user input. ๐Ÿ˜ญ

Like, seriously bestie, go enable the compiler warning -Wtautological-constant-out-of-range-compare too, 'cause this check does not go rummaging through compile-time constant loop boundaries โ€” that's on purpose, so it doesn't overlap with that warning's whole thing. Teamwork! ๐Ÿค

โœจ Options โœจ

  • MagnitudeBitsUpperLimit โ€” the upper limit for the magnitude bits of the loop variable. If you set it, the check filters out those catches where the loop variable's type has more magnitude bits than the upper limit you specified. Default value is 16. So like, if you set this option to 31 (bits), then a 32-bit unsigned int gets ignored by the check, but a 32-bit int does not get ignored (because a 32-bit signed int has 31 magnitude bits). Math! ๐Ÿ’…
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, bestie, for real: this check uses a flow-sensitive static analysis to produce its results. Which means it can be way more resource intensive (RAM, CPU, both ๐Ÿ˜ญ) than your average clang-tidy check. Your laptop has been warned.

This check sniffs out unsafe accesses to the values contained in std::optional<T>, absl::optional<T>, base::Optional<T>, folly::Optional<T>, bsl::optional, or BloombergLP::bdlb::NullableValue objects. And below, we're gonna refer to all of those types collectively as optional<T>. One big happy optional family ๐Ÿซถ.

An access to the value of an optional<T> happens when one of its value, operator*, or operator-> member functions gets invoked. Now โ€” to align with the common misconceptions out there, the check treats these member functions as equivalent, even though there are subtle differences related to exceptions versus undefined behavior. Peep the Additional notes below for more on that whole situation ๐Ÿ‘€.

An access to the value of an optional<T> counts as safe if and only if code in the local scope (like, a function body) ensures that the optional<T> has a value in all possible execution paths that can reach that 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>). Slay โœจ.

Below we list some examples, starting with the unsafe optional access patterns, followed by the safe access patterns. Buckle up ๐ŸŽข.

๐Ÿ’€ Unsafe access patterns

Access the value without checking if it exists โ€” the check flags accesses to the value that aren't 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 fully aware of the state of an optional object in different branches of the code. Observe:

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 knows that function results might not be stable. Like, consecutive calls to the same function might totally return different values. It's giving chaos:

void f(Foo foo) {
  if (foo.take().has_value()) {
    use(*foo.take()); // unsafe: it is unclear whether `foo.take()` has a value.
  }
}

Exception: accessor methods โ€” the check assumes accessor methods of a class are stable, using a heuristic to figure out which methods are accessors. Specifically, parameter-free const methods and smart pointer-like APIs (non-const overloads of * when there's a parallel const overload) are treated as accessors. Note that this is not guaranteed to be safe โ€” but it's widely used (safely) in practice, so. Calls to non-const methods are assumed to modify the state of the object and affect the stability of earlier accessor calls. Rude of them, honestly ๐Ÿ™ˆ.

Rely on invariants of uncommon APIs โ€” the check is unaware of invariants of uncommon APIs. Example time:

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. Look:

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 the straightforward ways for checking if a value exists and accessing the value contained in an optional object. Like so:

void f(std::optional<int> opt) {
  if (opt.has_value()) {
    use(*opt);
  }
}

Check if a value exists, then access the value from a copy โ€” the criteria the check uses are semantic, not syntactic. It recognizes when a copy of the optional object being accessed is known to have a value. Cute:

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:

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:

void f(std::optional<int> opt) {
  bool safe = false;
  if (opt.has_value() && SomeOtherCondition()) {
    safe = true;
  }
  // ... more code...
  if (safe) {
    use(*opt);
  }
}

Stabilize function results โ€” function results are not assumed to be stable across calls, except for const accessor methods. For more complex accessors (non-const, or ones that depend on multiple params) it's best to store the result of the function call in a local variable and then use that variable to access the value:

void f(Foo foo) {
  if (const auto& foo_opt = foo.take(); foo_opt.has_value()) {
    use(*foo_opt);
  }
}

Do not rely on uncommon-API invariants โ€” when uncommon APIs guarantee that an optional has contents, do not rely on it โ€” instead, check explicitly that the optional object has a value:

void f(Foo foo) {
  if (const auto& property = foo.GetProperty("bar")) {
    use(*property);
  }
}

...instead of that HasProperty, GetProperty pairing we saw above. Iconic behavior change tbh ๐Ÿ’….

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:

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);
  }
}

(yeah the braces are literally like that on the page, bestie โ€” not me, I promise ๐Ÿ™ˆ)

๐ŸŽ€ Additional notes

Aliases created via using declarations โ€” the check is aware of aliases of optional types that are created via using declarations:

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. In the meantime it's best to follow the same principles when using optionals in lambdas, bestie ๐Ÿงš.

Access with operator*() vs. value() โ€” okay so, 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)? Like, why is it considered unsafe to access an optional with value(), if it's not provably populated with a value? And for that matter, why is CHECK() followed by operator*() any better than value(), given that they're 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*() plus 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. Periodt. ๐Ÿ”ฅ

GoogleTest awareness โ€” the check recognizes common macros like ASSERT_TRUE and ASSERT_FALSE:

TEST(OptionalTest, CheckValue) {
  std::optional<int> opt;
  EXPECT_TRUE(opt.has_value());
  EXPECT_EQ(opt.value(), 42); // unsafe: EXPECT_TRUE doesn't terminate test.

  ASSERT_TRUE(opt.has_value());
  EXPECT_EQ(opt.value(), 42); // safe: ASSERT_TRUE terminates if no value.
}

Less common macros such as ASSERT_NE(..., nullopt) and ASSERT_THAT are not currently supported and get ignored, which may result in false positives. Sorry babes ๐Ÿ˜ญ.

โœจ Options โœจ

  • IgnoreSmartPointerDereference โ€” if set to true, the check ignores optionals that are reached through overloaded smart-pointer-like dereference (operator*, operator->) on classes other than the optional type itself. This helps dodge false positives where the analysis can't equate results across such calls. Heads up though: this does not cover access through operator[]. Default is false.
  • IgnoreValueCalls โ€” if you set this to true, the check justโ€ฆ doesn't diagnose calls to optional::value() anymore ๐Ÿ’…. Diagnostics for operator*() and operator->() stay enabled though. It's giving "for codebases that intentionally rely on value() for defined, guarded access while still flagging UB-prone operator dereferences" energy. Default is false.

๐Ÿ› bugprone-unchecked-string-to-number-conversion

Bestie, this check flags calls to string-to-number conversion functions that don't verify the validity of the conversion โ€” we're talking atoi() or scanf() type behavior ๐Ÿ™ˆ. It does not flag calls to strtol() or other related conversion functions that do better error checking, because those ones ate and left no crumbs โœจ.

#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 */
  }
}

๐Ÿ“š References

This check corresponds to the CERT C Coding Standard rule ERR34-C. Detect errors when converting a string to a number. iykyk ๐ŸŽ€


๐Ÿ› bugprone-undefined-memory-manipulation

Finds calls of memory manipulation functions memset(), memcpy() and memmove() on non-TriviallyCopyable objects, which is literally undefined behavior ๐Ÿ’€.

Using memory manipulation functions on non-TriviallyCopyable objects can lead to a whole range of subtle and challenging issues in C++ code, ngl. 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 challenging to identify and diagnose the root cause. Additionally, misuse of memory manipulation functions can bypass essential object-specific operations, such as constructors and destructors, leading to resource leaks or improper initialization. Not the vibe.

For example, when using memcpy to copy std::string, pointer data is being copied, and it can result in a double free issue ๐Ÿซ :

#include <cstring>
#include <string>

int main() {
    std::string source = "Hello";
    std::string destination;

    std::memcpy(&destination, &source, sizeof(std::string));

    // Undefined behavior may occur here, during std::string destructor call.
    return 0;
}

๐Ÿ› bugprone-undelegated-constructor

Finds creation of temporary objects in constructors that look like a function call to another constructor of the same class ๐Ÿ‘€.

The user most likely meant to use a delegating constructor or base class initializer. We've all been there, bestie ๐Ÿซถ.


๐Ÿ› bugprone-unhandled-code-paths

This check discovers situations where code paths are not fully-covered ๐Ÿ”.

if-else if chains that miss a final else branch might lead to unexpected program execution and be the result of a logical error. If the missing else branch is intended you can leave it empty with a clarifying comment โœ๏ธ. This warning can be noisy on some code bases, so it is disabled by default (she's shy).

void f1() {
  int i = determineTheNumber();

   if(i > 0) {
     // Some Calculation
   } else if (i < 0) {
     // Precondition violated or something else.
   }
   // ...
}

Similar arguments hold for switch statements which do not cover all possible code paths ๐ŸŒˆ.

// The missing default branch might be a logical error. It can be kept empty
// if there is nothing to do, making it explicit.
void f2(int i) {
  switch (i) {
  case 0: // something
    break;
  case 1: // something else
    break;
  }
  // All other numbers?
}

// Violates this rule as well, but already emits a compiler warning (-Wswitch).
enum Color { Red, Green, Blue, Yellow };
void f3(enum Color c) {
  switch (c) {
  case Red: // We can't drive for now.
    break;
  case Green:  // We are allowed to drive.
    break;
  }
  // Other cases missing
}

โœจ Options

  • WarnOnMissingElse โ€” boolean flag that activates a warning for missing else branches. Default is false.

๐Ÿ› bugprone-unhandled-exception-at-new

Finds calls to new with a missing exception handler for std::bad_alloc ๐Ÿ˜ฑ.

Calls to new may throw exceptions of type std::bad_alloc that should be handled. Alternatively, the nonthrowing form of new can be used. The check verifies that the exception is handled in the function that calls new.

If a nonthrowing version is used or the exception is allowed to propagate out of the function, no warning is generated ๐Ÿ‘Œ.

The exception handler is checked if it catches a std::bad_alloc or std::exception exception type, or all exceptions (catch-all). The check assumes that any user-defined operator new is either noexcept or may throw an exception of type std::bad_alloc (or one derived from it). Other exception class types are not taken into account, periodt.

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

Heads up: 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 by using the copy-and-swap or the copy-and-move method ๐Ÿ’….

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 self-copy assignment from ruining the object state. A typical use case is when the class has a pointer field and the copy assignment operator first releases the pointed object and then tries to assign it:

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  T& operator=(const T &rhs) {
    delete p;
    p = new int(*rhs.p);
    return *this;
  }
};

There are two common C++ patterns to avoid this problem. The first is the self-assignment check ๐Ÿฆ‹:

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  T& operator=(const T &rhs) {
    if(this == &rhs)
      return *this;

    delete p;
    p = new int(*rhs.p);
    return *this;
  }
};

The second one is the copy-and-swap method, when we create a temporary copy (using the copy constructor) and then swap this temporary object with this:

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  void swap(T &rhs) {
    using std::swap;
    swap(p, rhs.p);
  }

  T& operator=(const T &rhs) {
    T(rhs).swap(*this);
    return *this;
  }
};

There is a third pattern which is less common. Let's call it the copy-and-move method, when we create a temporary copy (using the copy constructor) and then move this temporary object into this (needs a move assignment operator):

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  T& operator=(const T &rhs) {
    T t = rhs;
    *this = std::move(t);
    return *this;
  }

  T& operator=(T &&rhs) {
    p = rhs.p;
    rhs.p = nullptr;
    return *this;
  }
};

โœจ Options

  • WarnOnlyIfThisHasSuspiciousField โ€” when true, 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 to true by default. ๐Ÿ‘‘

๐Ÿ› bugprone-unintended-char-ostream-output

Finds unintended character output from unsigned char and signed char to an ostream ๐Ÿ˜ญ.

Normally, when unsigned char (uint8_t) or signed char (int8_t) is used, it is more likely a number than a character. However, when it is passed directly to std::ostream's operator<<, the result is the character output instead of the numeric value. This often contradicts the developer's intent to print integer values. It's giving "why is my number a letter" ๐Ÿ’€.

uint8_t v = 65;
std::cout << v; // output 'A' instead of '65'

The check will suggest casting the value to an appropriate type to indicate the intent โ€” by default, it will cast to unsigned int for unsigned char and int for signed char โœจ.

std::cout << static_cast<unsigned int>(v); // when v is unsigned char
std::cout << static_cast<int>(v); // when v is signed char

To avoid lengthy cast statements, adding the prefix + to the variable can also suppress warnings, because the unary expression will promote the value to an int ๐Ÿ”ฅ.

std::cout << +v;

Or cast to char to explicitly indicate that output should be a character ๐ŸŽ€.

std::cout << static_cast<char>(v);

โœจ Options

  • AllowedTypes โ€” a semicolon-separated list of type names that will be treated like the char type: the check will not report variables declared with these types or explicit cast expressions to these types. Note that this distinguishes type aliases from the original type, so specifying e.g. unsigned char here will not suppress reports about uint8_t even if it is defined as a typedef alias for unsigned char ๐Ÿ™ˆ. Default is unsigned char;signed char.
  • CastTypeName โ€” when CastTypeName is specified, the fix-it will use CastTypeName as the cast target type. Otherwise, the fix-it will automatically infer the type ๐Ÿ’–.

๐Ÿ’… bugprone-unique-ptr-array-mismatch

Okay bestie, this check is literally catching that specific โ€œoopsโ€ where you make a C++ unique pointer to a non-array type, but then you initialize it with an array. Itโ€™s giving โ€œthe vibes are off and the memory is gonna leak.โ€

If a pointer std::unique_ptr<T> is initialized with a new-expression new T[], the memory is not deallocated correctly. A plain delete gets used in this case to deallocate the target memory โ€” and nope, thatโ€™s not the move. You need a delete[] call instead. A std::unique_ptr<T[]> uses the correct delete operator. Also, this check does not emit a warning if a unique_ptr with a user-specified deleter type is used. Custom deleter? Youโ€™re off the hook.

The check offers replacement of unique_ptr<T> to unique_ptr<T[]> if itโ€™s used at a single variable declaration (one variable in one statement). Slay when itโ€™s simple, no cap.

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. So yeah, itโ€™s a vibe check for one specific scenario, not the whole cinematic universe.


๐Ÿšจ bugprone-unsafe-functions

This one is highkey the group chatโ€™s safety inspector. It checks for functions that have safer, more secure replacements available, or that are considered deprecated due to design flaws. The check heavily relies on the functions from the Annex K. โ€œBounds-checking interfacesโ€ of C11. Iconic C11 behavior, honestly.

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. So if you see those names, itโ€™s the same bestie wearing a different jacket.

โœจ 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: strftime
  • gets, suggested replacement: fgets

The following functions are always checked, regardless of Annex K availability:

  • rewind, suggested replacement: fseek
  • setbuf, suggested replacement: setvbuf
  • std::get_temporary_buffer, suggested replacement: โ€œplainโ€ allocation with operator new[]

If ReportMoreUnsafeFunctions is enabled, the following functions are also checked:

  • bcmp, suggested replacement: memcmp
  • bcopy, suggested replacement: memcpy_s if Annex K is available, or memcpy
  • bzero, suggested replacement: memset_s if Annex K is available, or memset
  • getpw, suggested replacement: getpwuid
  • vfork, suggested replacement: posix_spawn

Although mentioned in the associated CERT rules, the following functions are ignored by the check:

atof, atoi, atol, atoll, tmpfile.

The availability of Annex K is determined based on the following macros:

  • __STDC_LIB_EXT1__: feature macro, which indicates the presence of Annex K. โ€œBounds-checking interfacesโ€ in the library implementation
  • __STDC_WANT_LIB_EXT1__: user-defined macro, which indicates that the user requests the functions from Annex K. to be defined.

Both macros have to be defined to suggest replacement functions from Annex K. __STDC_LIB_EXT1__ is defined by the library implementation, and __STDC_WANT_LIB_EXT1__ must be defined to 1 by the user before including any system headers. No sneaky shortcut, bestie.

๐ŸŽ€ Custom functions

The option CustomFunctions allows the user to define custom functions to be checked. The format is the following, without newlines:

bugprone-unsafe-functions.CustomFunctions="
  functionRegex1[, replacement1[, reason1]];
  functionRegex2[, replacement2[, reason2]];
  ...
"

The functions are matched using POSIX extended regular expressions. (Note: The regular expressions do not support negative (?!) matches.) Lowkey important, donโ€™t fight the regex.

The reason is optional and is used to provide additional information about the reasoning behind the replacement. The default reason is is marked as unsafe.

If replacement is empty, the default text it should not be used will be shown instead of the suggestion for a replacement.

If the reason starts with the character >, the reason becomes fully custom. The default suffix is disabled even if a replacement is present, and only the reason message is shown after the matched function, to allow better control over the suggestions. (The starting > and whitespace directly after it are trimmed from the message.) Thatโ€™s the main character customization option, fr fr.

As an example, the following configuration matches only the function original in the default namespace. A similar diagnostic can also be printed using a fully custom reason.

// bugprone-unsafe-functions.CustomFunctions:
//   ^original$, replacement, is deprecated;
// Using the fully custom message syntax:
//   ^suspicious$,,> should be avoided if possible.
original(); // warning: function 'original' is deprecated; 'replacement' should be used instead.
suspicious(); // warning: function 'suspicious' should be avoided if possible.
::std::original(); // no-warning
original_function(); // no-warning

If the regular expression contains the character :, it is matched against the qualified name (i.e. std::original), otherwise the regex is matched against the unqualified name (original). If the regular expression starts with :: (or ^::), it is matched against the fully qualified name (::std::original). This is the regex equivalent of knowing everyoneโ€™s full government name.

One of the use cases for fully custom messages is suggesting compiler options and warning flags:

// bugprone-unsafe-functions.CustomFunctions:
//   ^memcpy$,,>is recommended to have compiler hardening using '_FORTIFY_SOURCE';
//   ^printf$,,>is recommended to have the '-Werror=format-security' compiler warning flag;

memcpy(dest, src, 999'999); // warning: function 'memcpy' is recommended to have compiler hardening using '_FORTIFY_SOURCE'
printf(raw_str); // warning: function 'printf' is recommended to have the '-Werror=format-security' compiler warning flag
NOTE: Fully qualified names can contain template parameters on certain C++ classes, but not on C++ functions. Type aliases are resolved before matching. As an example, the member function open in the class std::ifstream has a fully qualified name of ::std::basic_ifstream<char>::open. The example could also be matched with the regex ::std::basic_ifstream<[^>]*>::open, which matches all potential template parameters, but does not match nested template classes.

โš™๏ธ Options

  • ReportMoreUnsafeFunctions โ€” when true, additional functions from widely used APIs (such as POSIX) are added to the list of reported functions. See the main documentation of the check for the complete list as to what this option enables. Default is true.
  • ReportDefaultFunctions โ€” when true, the check reports the default set of functions. Consider changing the setting to false if you only want to see custom functions matched via custom functions. Default is true.
  • CustomFunctions โ€” a semicolon-separated list of custom functions to be matched. A matched function contains a regular expression, an optional name of the replacement function, and an optional reason, separated by comma. For more information, see Custom functions.

๐ŸŒธ Examples

#ifndef __STDC_LIB_EXT1__
#error "Annex K is not supported by the current standard library implementation."
#endif

#define __STDC_WANT_LIB_EXT1__ 1

#include <string.h> // Defines functions from Annex K.
#include <stdio.h>

enum { BUFSIZE = 32 };

void Unsafe(const char *Msg) {
  static const char Prefix[] = "Error: ";
  static const char Suffix[] = "\n";
  char Buf[BUFSIZE] = {0};

  strcpy(Buf, Prefix); // warning: function 'strcpy' is not bounds-checking; 'strcpy_s' should be used instead.
  strcat(Buf, Msg);    // warning: function 'strcat' is not bounds-checking; 'strcat_s' should be used instead.
  strcat(Buf, Suffix); // warning: function 'strcat' is not bounds-checking; 'strcat_s' should be used instead.
  if (fputs(buf, stderr) < 0) {
    // error handling
    return;
  }
}

void UsingSafeFunctions(const char *Msg) {
  static const char Prefix[] = "Error: ";
  static const char Suffix[] = "\n";
  char Buf[BUFSIZE] = {0};

  if (strcpy_s(Buf, BUFSIZE, Prefix) != 0) {
    // error handling
    return;
  }

  if (strcat_s(Buf, BUFSIZE, Msg) != 0) {
    // error handling
    return;
  }

  if (strcat_s(Buf, BUFSIZE, Suffix) != 0) {
    // error handling
    return;
  }

  if (fputs(Buf, stderr) < 0) {
    // error handling
    return;
  }
}

๐Ÿ’€ bugprone-unsafe-to-allow-exceptions

Finds functions where throwing exceptions is unsafe but the function is still marked as potentially throwing. Throwing exceptions from the following functions can be problematic:

  • Destructors
  • Move constructors
  • Move assignment operators
  • The main() functions
  • swap() functions
  • iter_swap() functions
  • iter_move() functions

Okay so, a destructor that throws an exception? That can cause undefined behavior, resource leaks, or your program just unexpectedly terminating. A move constructor or move assignment that throws can also cause undefined behavior or a resource leak. swap() operations are expected to be non-throwing in most cases, and itโ€™s always possible to implement them in a non-throwing way. Non-throwing swap() operations are also used to create move operations. And a main() function that throws? Also unexpected termination, bestie.

This check flags any of these functions if itโ€™s marked noexcept(false) or throw(exception). Thatโ€™s a signal that the function is expected to throw exceptions. It only checks whether those keywords are present โ€” not whether the function actually throws anything. If you want to check whether the function actually throws, use bugprone-exception-escape (but heads up: it does not warn if a function is explicitly marked as throwing).

โœจ Options โœจ

  • CheckedSwapFunctions โ€” semicolon-separated list of checked swap function names (where throwing exceptions is unsafe). These functions are checked if the parameter count is at least 1. Default value is swap;iter_swap;iter_move.

๐Ÿงš bugprone-unused-local-non-trivial-variable ๐Ÿงš

Warns when a local non-trivial variable is unused within a function. The following types of variables are excluded from this check:

  • trivial and trivially copyable
  • references and pointers
  • exception variables in catch clauses
  • static or thread local
  • structured bindings
  • variables with [[maybe_unused]] attribute
  • name-independent variables

This check can be configured to warn on all non-trivial variables by setting IncludeTypes to .*, and excluding specific types using ExcludeTypes.

In this example, my_lock would generate a warning that itโ€™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 the IncludeTypes matches. By default it is an empty list.

๐ŸŽ€ bugprone-unused-raii ๐ŸŽ€

Finds temporaries that look like RAII objects.

The canonical example for this is a scoped lock.

{
  scoped_lock(&global_mutex);
  critical_section();
}

The destructor of the scoped_lock is called before the critical_section is entered, leaving it unprotected.

We apply a number of heuristics to reduce the false positive count of this check:

  • Ignore code expanded from macros. Testing frameworks make heavy use of this.
  • Ignore types with trivial destructors. They are very unlikely to be RAII objects and thereโ€™s no difference when they are deleted.
  • Ignore objects at the end of a compound statement (doesnโ€™t change behavior).
  • Ignore objects returned from a call.

๐ŸŒธ bugprone-unused-return-value ๐ŸŒธ

Warns on unused function return values. The checked functions can be configured.

Operator overloading with assignment semantics are ignored.

โœจ 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() and std::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 the release() return value indicates that the programmer confused the function with reset().
  • std::basic_string::empty() and std::vector::empty(). Not using the return value often indicates that the programmer confused the function with clear().
  • 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. Periodt.


๐Ÿฆ‹ bugprone-use-after-move ๐Ÿฆ‹

Warns if an object is used after it has been moved, for example:

std::string str = "Hello, world!\n";
std::vector<std::string> messages;
messages.emplace_back(std::move(str));
std::cout << str;

The last line will trigger a warning that str is used after it has been moved.

The check does not trigger a warning if the object is reinitialized after the move and before the use. For example, no warning will be output for this code:

messages.emplace_back(std::move(str));
str = "Greetings, stranger!\n";
std::cout << str;

Subsections below explain more precisely what exactly the check considers to be a move, use, and reinitialization.

The check takes control flow into account. A warning is only emitted if the use can be reached from the move. This means that the following code does not produce a warning:

if (condition) {
  messages.emplace_back(std::move(str));
} else {
  std::cout << str;
}

On the other hand, the following code does produce a warning:

for (int i = 0; i < 10; ++i) {
  std::cout << str;
  messages.emplace_back(std::move(str));
}

(The use-after-move happens on the second iteration of the loop.)

In some cases, the check may not be able to detect that two branches are mutually exclusive. For example (assuming that i is an int):

if (i == 1) {
  messages.emplace_back(std::move(str));
}
if (i == 2) {
  std::cout << str;
}

In this case, the check will erroneously produce a warning, even though it is not possible for both the move and the use to be executed. More formally, the analysis is flow-sensitive but not path-sensitive.

๐Ÿ’… Silencing erroneous warnings ๐Ÿ’…

An erroneous warning can be silenced by reinitializing the object after the move:

if (i == 1) {
  messages.emplace_back(std::move(str));
  str = "";
}
if (i == 2) {
  std::cout << str;
}

If you want to avoid the overhead of actually reinitializing the object, you can create a dummy function that causes the check to assume the object was reinitialized:

template <class T>
void IS_INITIALIZED(T&) {}

You can use this as follows:

if (i == 1) {
  messages.emplace_back(std::move(str));
}
if (i == 2) {
  IS_INITIALIZED(str);
  std::cout << str;
}

The check will not output a warning in this case because passing the object to a function as a non-const pointer or reference counts as a reinitialization (see section Reinitialization below).

๐ŸŒˆ Unsequenced moves, uses, and reinitializations ๐ŸŒˆ

In many cases, C++ does not make any guarantees about the order in which sub-expressions of a statement are evaluated. This means that in code like the following, it is not guaranteed whether the use will happen before or after the move:

void f(int i, std::vector<int> v);
std::vector<int> v = { 1, 2, 3 };
f(v[1], std::move(v));

In this kind of situation, the check will note that the use and move are unsequenced.

The check will also take sequencing rules into account when reinitializations occur in the same statement as moves or uses. A reinitialization is only considered to reinitialize a variable if it is guaranteed to be evaluated after the move and before the use.

โญ Move โญ

The check currently only considers calls of std::move on local variables or function parameters. It does not check moves of member variables or global variables.

Any call of std::move on a variable is considered to cause a move of that variable โ€” even if the result of std::move isn't actually passed to an rvalue reference parameter.

So like, this means the check will flag a use-after-move even on a type that doesn't define a move constructor or move assignment operator. And that's intentional ๐Ÿ’…. Devs may use std::move on such a type expecting that the type will add move semantics in the future. If such a std::move has the potential to cause a use-after-move, the check wants to warn about it even if the type doesn't implement move semantics yet.

Furthermore, if the result of std::move is passed to an rvalue reference parameter, this will always be considered to cause a move โ€” even if the function that consumes this parameter doesn't move from it, or does so only conditionally. For example, in the following situation, the check will assume that a move always takes place:

std::vector<std::string> messages;
void f(std::string &&str) {
  // Only remember the message if it isn't empty.
  if (!str.empty()) {
    messages.emplace_back(std::move(str));
  }
}
std::string str = "";
f(std::move(str));

The check will assume that the last line causes a move, even though in this particular case it does not. Again โ€” intentional, bestie, it's by design.

There's one special case ๐ŸŽ€: a call to std::move inside a try_emplace call is conservatively assumed not to move. This is to avoid spurious warnings, because the check has no way to reason about the bool returned by try_emplace.

When analyzing the order in which moves, uses and reinitializations happen (see the 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 next bit of code will also trigger a use-after-move warning ๐Ÿ‘€:

void consume(int);

void f(int&& i) {
  consume(std::forward<int>(i));
  consume(std::forward<int>(i)); // use-after-move
}

Use ๐ŸŒธ

Any occurrence of the moved variable that isn't a reinitialization (see below) or an explicit call to the variable destructor 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, 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 โ€” because those would be dereferencing a nullptr ๐Ÿ’€.

User-defined types can be annotated as having the same semantics as standard smart pointers with [[clang::annotate("clang-tidy", "bugprone-use-after-move", "null_after_move")]]. This expresses that a moved-from object of this type is a null pointer.

If multiple uses occur after a move, only the first of these is flagged.

Reinitialization โœจ

The check considers a variable to be reinitialized in the following cases:

  • The variable occurs on the left-hand side of an assignment.
  • The variable is passed to a function as a non-const pointer or non-const lvalue reference. (It's assumed that the variable may be an out-parameter for the function.)
  • clear() or assign() is called on the variable and the variable is of one of the standard container types basic_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 type std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional, or std::any.
  • A member function marked with the [[clang::reinitializes]] attribute is called on the variable.
  • The variable is passed as an argument to std::tie on the left-hand side of an assignment (e.g. std::tie(a, b) = f(...)). The tuple assignment operator writes back through the stored references, which reinitializes each named 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, because this pattern of reinitializing a struct is error-prone ๐Ÿ˜ญ. For example, if an additional member variable is added to S, it's easy to forget to add the reinitialization for this additional member. Instead, it's safer to assign to the entire struct in one go, and this will also avoid the use-after-move warning. ๐Ÿซถ

Options ๐ŸŽ€

  • InvalidationFunctions โ€” a semicolon-separated list of regular expressions matching names of functions that cause their first arguments to be invalidated (e.g., closing a handle). For member functions, the first argument is considered to be the implicit object argument (this). Default value is an empty string.
  • ReinitializationFunctions โ€” a semicolon-separated list of regular expressions matching names of functions that reinitialize the object. For member functions, the implicit object argument (*this) is considered to be reinitialized. For non-member or static member functions, the first argument is considered to be reinitialized. Default value is an empty string.

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.

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

The cert-dcl50-cpp check is an alias โ€” please see modernize-avoid-variadic-functions for more information.

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

The cert-dcl58-cpp is an alias โ€” please see bugprone-std-namespace-modification for more information.

This check corresponds to the CERT C++ Coding Standard rule DCL58-CPP. Do not modify the standard namespaces.

cert-dcl59-cpp

The cert-dcl59-cpp check is an alias โ€” please see misc-anonymous-namespace-in-header for more information.

cert-env33-c

The cert-env33-c check is an alias โ€” please see bugprone-command-processor for more information.

This check corresponds to the CERT C Coding Standard rule ENV33-C. Do not call system().

cert-err09-cpp

The cert-err09-cpp check is an alias โ€” please see misc-throw-by-value-catch-by-reference for more information.

This check corresponds to the CERT C++ Coding Standard recommendation ERR09-CPP. Throw anonymous temporaries. However ๐Ÿ˜ญ all of the CERT recommendations have been removed from public view, and so their justification for the behavior of this check requires an account on their wiki to view. rip ๐Ÿ’€

cert-err33-c

Warns on unused function return values. Many of the standard library functions return a value that indicates if the call was successful. Ignoring the returned value can cause unexpected behavior if an error has occurred. The following functions are checked:

  • aligned_alloc()
  • asctime_s()
  • at_quick_exit()
  • atexit()
  • bsearch()
  • bsearch_s()
  • btowc()
  • c16rtomb()
  • c32rtomb()
  • calloc()
  • clock()
  • cnd_broadcast()
  • cnd_init()
  • cnd_signal()
  • cnd_timedwait()
  • cnd_wait()
  • ctime_s()
  • fclose()
  • fflush()
  • fgetc()
  • fgetpos()
  • fgets()
  • fgetwc()
  • fopen()
  • fopen_s()
  • fprintf()
  • fprintf_s()
  • fputc()
  • fputs()
  • fputwc()
  • fputws()
  • fread()
  • freopen()
  • freopen_s()
  • fscanf()
  • fscanf_s()
  • fseek()
  • fsetpos()
  • ftell()
  • fwprintf()
  • fwprintf_s()
  • fwrite()
  • fwscanf()
  • fwscanf_s()
  • getc()
  • getchar()
  • getenv()
  • getenv_s()
  • gets_s()
  • getwc()
  • getwchar()
  • gmtime()
  • gmtime_s()
  • localtime()
  • localtime_s()
  • malloc()
  • mbrtoc16()
  • mbrtoc32()
  • mbsrtowcs()
  • mbsrtowcs_s()
  • mbstowcs()
  • mbstowcs_s()
  • memchr()
  • mktime()
  • mtx_init()
  • mtx_lock()
  • mtx_timedlock()
  • mtx_trylock()
  • mtx_unlock()
  • printf_s()
  • putc()
  • putwc()
  • raise()
  • realloc()
  • remove()
  • rename()
  • setlocale()
  • setvbuf()
  • scanf()
  • scanf_s()
  • signal()
  • snprintf()
  • snprintf_s()
  • sprintf()
  • sprintf_s()
  • sscanf()
  • sscanf_s()
  • strchr()
  • strerror_s()
  • strftime()
  • strpbrk()
  • strrchr()
  • strstr()
  • strtod()
  • strtof()
  • strtoimax()

(still rolling through the roster, bestie โ€” the list keeps going, don't panic ๐Ÿ’–)

  • 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()

So like โ€” this check is literally an alias of bugprone-unused-return-value, just with a fixed set of functions baked in. Same energy, curated guest list. ๐Ÿ’…

Suppressing issues by casting to void is enabled by default, and you can totally turn that off by setting the AllowCastToVoid option to false. No cap.

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 is taken from that rule, with the following exception:

  • The check can't differentiate if a function is called with a NULL argument. Therefore the following functions are not checked: mblen, mbrlen, mbrtowc, mbtowc, wctomb, wctomb_s

๐Ÿซถ cert-err34-c

The cert-err34-c check is an alias โ€” please see bugprone-unchecked-string-to-number-conversion for more information.

๐Ÿซถ cert-err52-cpp

The cert-err52-cpp check is an alias โ€” please see modernize-avoid-setjmp-longjmp for more information.

This check corresponds to the CERT C++ Coding Standard rule ERR52-CPP. Do not use setjmp() or longjmp().

๐Ÿซถ cert-err58-cpp

The cert-err58-cpp check is an alias โ€” please see bugprone-throwing-static-initialization for more information.

This check corresponds to the CERT C++ Coding Standard rule ERR58-CPP. Handle all exceptions thrown before main() begins executing.

๐Ÿซถ cert-err60-cpp

The cert-err60-cpp check is an alias โ€” please see bugprone-exception-copy-constructor-throws for more information.

This check corresponds to the CERT C++ Coding Standard rule ERR60-CPP. Exception objects must be nothrow copy constructible.

๐Ÿซถ cert-err61-cpp

The cert-err61-cpp check is an alias โ€” please see misc-throw-by-value-catch-by-reference for more information.

๐Ÿซถ cert-exp42-c

The cert-exp42-c check is an alias โ€” please see bugprone-suspicious-memory-comparison for more information.

๐Ÿซถ cert-exp45-c

The cert-exp45-c check is an alias โ€” please see bugprone-assignment-in-selection-statement for more information.

๐Ÿซถ cert-fio38-c

The cert-fio38-c check is an alias โ€” please see misc-non-copyable-objects for more information.

This check corresponds to CERT C++ Coding Standard rule FIO38-C. Do not copy a FILE object.

๐Ÿซถ cert-flp30-c

The cert-flp30-c check is an alias โ€” please see bugprone-float-loop-counter for more information

This check corresponds to the CERT C Coding Standard rule FLP30-C. Do not use floating-point variables as loop counters.

๐Ÿซถ cert-flp37-c

The cert-flp37-c check is an alias โ€” please see bugprone-suspicious-memory-comparison for more information.

๐Ÿซถ cert-int09-c

The cert-int09-c check is an alias โ€” please see readability-enum-initial-value for more information.

๐Ÿซถ cert-mem57-cpp

The cert-mem57-cpp is an alias โ€” please see bugprone-default-operator-new-on-overaligned-type for more information.

This check corresponds to the CERT C++ Coding Standard rule MEM57-CPP. Avoid using default operator new for over-aligned types.

๐Ÿซถ cert-msc24-c

The cert-msc24-c check is an alias โ€” please see bugprone-unsafe-functions for more information.

๐Ÿซถ cert-msc30-c

The cert-msc30-c check is an alias โ€” please see misc-predictable-rand for more information.

This check corresponds to the CERT C Coding Standard rule MSC30-C. Do not use the rand() function for generating pseudorandom numbers.

๐Ÿซถ cert-msc32-c

The cert-msc32-c check is an alias โ€” please see bugprone-random-generator-seed for more information.

This check corresponds to the CERT C Coding Standard rule MSC32-C. Properly seed pseudorandom number generators.

๐Ÿซถ cert-msc33-c

The cert-msc33-c check is an alias โ€” please see bugprone-unsafe-functions for more information.

๐Ÿซถ cert-msc50-cpp

The cert-msc50-cpp check is an alias โ€” please see misc-predictable-rand for more information.

This check corresponds to the CERT C Coding Standard rule MSC50-CPP. Do not use std::rand() for generating pseudorandom numbers.

๐Ÿซถ cert-msc51-cpp

The cert-msc51-cpp check is an alias โ€” please see bugprone-random-generator-seed for more information.

This check corresponds to the CERT C++ Coding Standard rule MSC51-CPP. Ensure your random number generator is properly seeded.

๐Ÿซถ cert-msc54-cpp

The cert-msc54-cpp check is an alias โ€” please see bugprone-signal-handler for more information.

๐Ÿซถ cert-oop11-cpp

The cert-oop11-cpp check is an alias โ€” please see performance-move-constructor-init for more information.

This check corresponds to the CERT C++ Coding Standard recommendation OOP11-CPP. Do not copy-initialize members or base classes from a move constructor. However โ€” and this is lowkey annoying โ€” 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. ๐Ÿ™ˆ

๐Ÿซถ cert-oop54-cpp

The cert-oop54-cpp check is an alias โ€” please see bugprone-unhandled-self-assignment for more information.

๐Ÿซถ cert-oop57-cpp

The cert-oop57-cpp check is an alias โ€” please see bugprone-raw-memory-call-on-non-trivial-type for more information.

This check corresponds to the CERT C++ Coding Standard rule OOP57-CPP. Prefer special member functions and overloaded operators to C Standard Library functions.

๐Ÿซถ cert-oop58-cpp

The cert-oop58-cpp check is an alias โ€” please see bugprone-copy-constructor-mutates-argument for more information.

๐Ÿซถ cert-pos44-c

The cert-pos44-c check is an alias โ€” please see bugprone-bad-signal-to-kill-thread for more information.

๐Ÿซถ cert-pos47-c

The cert-pos47-c check is an alias โ€” please see concurrency-thread-canceltype-asynchronous for more information.

๐Ÿซถ cert-sig30-c

The cert-sig30-c check is an alias โ€” please see bugprone-signal-handler for more information.

๐Ÿซถ cert-str34-c

The cert-str34-c check is an alias โ€” please see bugprone-signed-char-misuse for more information.


๐Ÿฆ‹ clang-analyzer-core.BitwiseShift

Finds cases where a bitwise shift operation causes undefined behaviour. Not the vibe. ๐Ÿ’€

The clang-analyzer-core.BitwiseShift check is an alias โ€” please see Clang Static Analyzer Available Checkers for more information.

๐Ÿฆ‹ clang-analyzer-core.CallAndMessage

Checks for logical errors in function calls and Objective-C message expressions (like uninitialized arguments, or null function pointers). Yeah, that's giving bugs.

The clang-analyzer-core.CallAndMessage check is an alias โ€” please see Clang Static Analyzer Available Checkers for more information.

๐Ÿฆ‹ clang-analyzer-core.DivideZero

Checks for division by zero. Ma'am, that's illegal. ๐Ÿ˜ญ

The clang-analyzer-core.DivideZero check is an alias โ€” please see Clang Static Analyzer Available Checkers for more information.

๐Ÿฆ‹ clang-analyzer-core.NonNullParamChecker

Checks 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

Checks 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

Checks that addresses to stack memory do not escape the function. Keep those addresses indoors, 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

Checks 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

Checks for declarations of a 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

Checks 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

Checks 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

Checks 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

Checks for blocks that capture uninitialized values. Sneaky little gremlins.

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

Checks 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

Checks 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 โ€” catches the moment an array of polymorphic objects gets destructed as its base class, which is a whole mess ๐Ÿ’€. It's an alias bestie, so the real tea is in Clang Static Analyzer Available Checkers.
  • clang-analyzer-cplusplus.InnerPointer โ€” checks for inner pointers belonging to C++ containers getting used after they've been reallocated or deallocated. It's an alias โ€” more info lives in Clang Static Analyzer Available Checkers ๐Ÿซถ.
  • clang-analyzer-cplusplus.Move โ€” sniffs out use-after-move bugs in C++, iconic behavior honestly. Yeah it's an alias, see Clang Static Analyzer Available Checkers for the full picture.
  • clang-analyzer-cplusplus.NewDelete โ€” catches double-free and use-after-free problems, and traces memory managed by new/delete โœจ. Alias alert: Clang Static Analyzer Available Checkers has the details.
  • clang-analyzer-cplusplus.NewDeleteLeaks โ€” the leak-hunting sibling: checks for memory leaks and traces memory managed by new/delete ๐Ÿ“. It's an alias, hit up Clang Static Analyzer Available Checkers.
  • clang-analyzer-cplusplus.PlacementNew โ€” makes sure default placement new is being handed pointers with enough storage capacity, no cap ๐ŸŽ€. Alias, so Clang Static Analyzer Available Checkers is where you go.
  • clang-analyzer-cplusplus.SelfAssignment โ€” checks C++ copy and move assignment operators for self assignment, the classic x = x jump-scare ๐Ÿ’…. It's an alias โ€” Clang Static Analyzer Available Checkers, bestie.
  • clang-analyzer-cplusplus.StringChecker โ€” checks C++ std::string bugs, and honestly std::string has main character energy in the bug department ๐Ÿ˜ญ. Alias, so see Clang Static Analyzer Available Checkers.
  • clang-analyzer-deadcode.DeadStores โ€” check for values stored to variables that are never read afterwards, aka the data just sitting there like ๐Ÿง. It's an alias, please see Clang Static Analyzer Available Checkers.
  • clang-analyzer-fuchsia.HandleChecker โ€” a checker that detect leaks related to Fuchsia handles ๐Ÿ‘‘. Yep, alias โ€” Clang Static Analyzer Available Checkers for more.
  • clang-analyzer-nullability.NullPassedToNonnull โ€” warns when a null pointer gets passed to a pointer that has a _Nonnull type, iykyk ๐Ÿ™ˆ. The alias life: see Clang Static Analyzer Available Checkers.
  • clang-analyzer-nullability.NullReturnedFromNonnull โ€” warns when a null pointer is returned from a function that has a _Nonnull return type, which is giving broken promise honestly ๐Ÿ’”. Alias โ€” check Clang Static Analyzer Available Checkers.
  • clang-analyzer-nullability.NullableDereferenced โ€” warns when a nullable pointer is dereferenced, which is like. girl. don't ๐Ÿ˜ญ. It's an alias, so Clang Static Analyzer Available Checkers it is.
  • clang-analyzer-nullability.NullablePassedToNonnull โ€” warns when a nullable pointer is passed to a pointer which has a _Nonnull type ๐Ÿ”ฅ. Alias โ€” Clang Static Analyzer Available Checkers has you covered.
  • clang-analyzer-nullability.NullableReturnedFromNonnull โ€” warns when a nullable pointer is returned from a function that has a _Nonnull return type. Alias behavior, so please see Clang Static Analyzer Available Checkers ๐ŸŒธ.
  • clang-analyzer-optin.core.EnumCastOutOfRange โ€” checks integer to enumeration casts for out of range values, lowkey a big deal ๐Ÿ’œ. It's an alias, go read Clang Static Analyzer Available Checkers.
  • clang-analyzer-optin.cplusplus.UninitializedObject โ€” reports uninitialized fields after object construction, which is giving "forgot to fill in the form" ๐Ÿ’€. Alias โ€” Clang Static Analyzer Available Checkers for the lore.
  • clang-analyzer-optin.cplusplus.VirtualCall โ€” checks virtual function calls during construction/destruction, the classic "why is my override not overriding" moment ๐Ÿ˜ญ. Alias, see Clang Static Analyzer Available Checkers.
  • clang-analyzer-optin.mpi.MPI-Checker โ€” checks MPI code. That's it, that's the vibe โœจ. It's an alias โ€” Clang Static Analyzer Available Checkers knows more.
  • clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker โ€” checks that NSLocalizedString macros include a comment for context ๐ŸŽ€. Alias energy: please see Clang Static Analyzer Available Checkers.
  • clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker โ€” warns about uses of non-localized NSStrings passed to UI methods expecting localized NSStrings ๐ŸŒˆ. It's an alias, so Clang Static Analyzer Available Checkers is the spot.
  • clang-analyzer-optin.performance.GCDAntipattern โ€” checks for performance anti-patterns when you're using Grand Central Dispatch, no cap ๐Ÿฆ‹. Alias โ€” see Clang Static Analyzer Available Checkers.
  • clang-analyzer-optin.performance.Padding โ€” checks for excessively padded structs, your struct is wearing shoulder pads it doesn't need ๐Ÿ’…. Alias, so Clang Static Analyzer Available Checkers.
  • clang-analyzer-optin.portability.UnixAPI โ€” finds implementation-defined behavior in UNIX/Posix functions, sneaky sneaky ๐Ÿ‘€. It's an alias โ€” full details in Clang Static Analyzer Available Checkers.
  • clang-analyzer-optin.taint.TaintedAlloc โ€” checks for memory allocations where the size parameter might be a tainted (attacker controlled) value, which is terrifying actually ๐Ÿ”ฅ. Alias, go see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.API โ€” checks for proper uses of various Apple APIs ๐Ÿ“. Yep, an alias โ€” Clang Static Analyzer Available Checkers for more.
  • clang-analyzer-osx.NumberObjectConversion โ€” checks for erroneous conversions of objects representing numbers into numbers, which sounds fake but is very real ๐Ÿ’€. Alias โ€” Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.ObjCProperty โ€” checks for proper uses of Objective-C properties ๐Ÿซถ. It's an alias, please see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.SecKeychainAPI โ€” checks for proper uses of Secure Keychain APIs, keep those secrets safe bestie ๐Ÿ”. Alias, so Clang Static Analyzer Available Checkers has the info.
  • clang-analyzer-osx.cocoa.AtSync โ€” checks for nil pointers being used as mutexes for @synchronized โœจ. It's an alias โ€” Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.AutoreleaseWrite โ€” warns about potentially crashing writes to autoreleasing objects from different autoreleasing pools in Objective-C, which is a whole crash arc ๐Ÿ˜ญ. Alias, see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.ClassRelease โ€” checks for sending 'retain', 'release', or 'autorelease' directly to a Class, and no that's not how you do it ๐Ÿ’…. Alias โ€” Clang Static Analyzer Available Checkers has the details.
  • clang-analyzer-osx.cocoa.Dealloc โ€” warns about Objective-C classes that lack a correct implementation of -dealloc, which is giving unfinished homework ๐ŸŽ€. It's an alias, please see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.IncompatibleMethodTypes โ€” warns about Objective-C method signatures with type incompatibilities, the "these two are NOT the same" of it all ๐Ÿ”ฅ. Alias โ€” Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.Loops โ€” improved modeling of loops using Cocoa collection types, slay ๐ŸŒธ. It's an alias, so go see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.MissingSuperCall โ€” warns about Objective-C methods that lack a necessary call to super, which is like ghosting your parent class ๐Ÿ™ˆ. Alias โ€” Clang Static Analyzer Available Checkers knows.
  • clang-analyzer-osx.cocoa.NSAutoreleasePool โ€” warns for suboptimal uses of NSAutoreleasePool in Objective-C GC mode ๐Ÿ’œ. Yep, alias โ€” please see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.NSError โ€” checks usage of NSError** parameters, all those double stars are doing a lot ๐Ÿ˜ญ. It's an alias โ€” Clang Static Analyzer Available Checkers for the full thing.
  • clang-analyzer-osx.cocoa.NilArg โ€” checks for prohibited nil arguments to ObjC method calls, because handing nil to something that said "no nil" is not the move ๐Ÿ’€. Alias, see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.NonNilReturnValue โ€” models the APIs that are guaranteed to return a non-nil value ๐Ÿ‘‘. Alias โ€” Clang Static Analyzer Available Checkers has more.
  • clang-analyzer-osx.cocoa.ObjCGenerics โ€” checks for type errors when using Objective-C generics ๐Ÿฆ‹. It's an alias, so please see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.RetainCount โ€” checks for leaks and improper reference count management, the classic retain/release cardio workout ๐Ÿ”ฅ. Alias โ€” Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak โ€” checks for leaked memory in autorelease pools that will never be drained, abandoned pools bestie ๐Ÿ˜ญ. It's an alias, see Clang Static Analyzer Available Checkers.
  • clang-analyzer-osx.cocoa.SelfInit โ€” checks that 'self' is properly initialized inside an initializer method ๐Ÿ’–.

the alias tea continues, bestie โ˜•โœจ

The clang-analyzer-osx.cocoa.SelfInit check is literally an alias โ€” please see Clang Static Analyzer Available Checkers for more information. ๐Ÿซถ


clang-analyzer-osx.cocoa.SuperDealloc

Warns you when someone's out here using '[super dealloc]' improperly in Objective-C. It's giving "I learned ARC from a 2009 blog post" energy ๐Ÿ’€

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 โ€” just sitting there, main character energy, contributing nothing to the plot. ๐Ÿ™ˆ

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 passing non-Objective-C types to variadic collection initialization methods that expect only Objective-C types. Like, read the room โ€” those methods only take Objective-C types, periodt. ๐Ÿ’…

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 usage of CFErrorRef* parameters. Yes bestie, the star is part of it โญ

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. Use them right or don't use them at all, iykyk. ๐Ÿ”ฅ

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. Don't hand these functions a null, they will NOT be slaying. ๐Ÿ˜ญ

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 when using 'CFArray' API. That index said "not today" and honestly good for it ๐Ÿ’€

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 if 'CFArray', 'CFDictionary', 'CFSet' are created with non-pointer-size values. Pointer-sized or it's not coming to the party ๐ŸŽ€

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 a loop counter is the red flag, bestie ๐Ÿšฉ

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 which pass a pointer to a stack-allocated (automatic) array as the argument. Function putenv does not copy the passed string, only a pointer to the data is stored and this data can be read even by other threads. Content of a stack-allocated array is likely to be overwritten after exiting from the function. ๐Ÿซ 

Bestie, real talk: this one is a whole mess. putenv keeps the pointer, doesn't copy anything, other threads can read it, and then your stack array gets clobbered the moment the function returns. Not it.

The clang-analyzer-security.PutenvStackArray check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.SetgidSetuidOrder

The checker checks for sequences of setuid(getuid()) and setgid(getgid()) calls (in this order). If such a sequence is found and there is no other privilege-changing function call (seteuid, setreuid, setresuid and the GID versions of these) in between, a warning is generated. The checker finds only exactly setuid(getuid()) calls (and the GID versions), not for example if the result of getuid() is stored in a variable. ๐Ÿ‘‘

Ngl the pattern matching here is strict. Store the result of getuid() in a variable first and the checker will straight-up not notice you. It wants the exact call, no middlemen ๐Ÿ’…

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. That pointer is giving expired-subscription behavior ๐Ÿ’€

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. If your buffer function has a "vintage" era, that's not a flex ๐ŸŽ€

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 be always checked. Yes, always. Not "when I feel like it." ๐Ÿ‘€

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. She's old, she's deprecated, let her rest โœจ

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. Same vibe, different fossil ๐Ÿฆ‹

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 on uses of the 'bzero' function. Ancient behavior, honestly ๐ŸŒธ

The clang-analyzer-security.insecureAPI.bzero check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.insecureAPI.decodeValueOfObjCType

Warns on uses of the '-decodeValueOfObjCType:at:' method. (Note the colons, bestie โ€” copy it exactly ๐Ÿ™ˆ)

The clang-analyzer-security.insecureAPI.decodeValueOfObjCType check is an alias, please see Clang Static Analyzer security.insecureAPI.decodeValueOfObjCType for more information.

Okay so this one's see-also is written differently from all the others โ€” it points at security.insecureAPI.decodeValueOfObjCType instead of the generic checkers page. Not me noticing ๐Ÿ‘€

clang-analyzer-security.insecureAPI.getpw

Warns on uses of the 'getpw' function. Sir, this is a Wendy's ๐Ÿ’€

The clang-analyzer-security.insecureAPI.getpw check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.insecureAPI.gets

Warns on uses of the 'gets' function. We don't say that name here. (@ the entire security community) ๐Ÿ˜ญ

The clang-analyzer-security.insecureAPI.gets check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.insecureAPI.mkstemp

Warns when 'mkstemp' is passed fewer than 6 X's in the format string. Six X's minimum, that's the whole dress code ๐ŸŽ€

The clang-analyzer-security.insecureAPI.mkstemp check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.insecureAPI.mktemp

Warns on uses of the 'mktemp' function. Girl, no. We use mkstemp now. ๐Ÿ”ฅ

The clang-analyzer-security.insecureAPI.mktemp check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.insecureAPI.rand

Warns on uses of the 'rand', 'random', and related functions. For security?! In this economy?! ๐Ÿ’€

The clang-analyzer-security.insecureAPI.rand check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.insecureAPI.strcpy

Warns on uses of the 'strcpy' and 'strcat' functions. Grouping them together because they're both serving chaos ๐Ÿซ 

The clang-analyzer-security.insecureAPI.strcpy check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-security.insecureAPI.vfork

Warns on uses of the 'vfork' function. Special little creature. Handle with caution ๐Ÿ’…

The clang-analyzer-security.insecureAPI.vfork check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.API

Checks calls to various UNIX/Posix functions. The whole family. Everyone gets checked. ๐ŸŒˆ

The clang-analyzer-unix.API check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.BlockInCriticalSection

Checks for calls to blocking functions inside a critical section. Bestie that's a deadlock speedrun ๐Ÿ’€

The clang-analyzer-unix.BlockInCriticalSection check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.Errno

Checks for improper use of 'errno'. She has rules, respect them โญ

The clang-analyzer-unix.Errno check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.Malloc

Checks for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free(). The full toxic-relationship trilogy, all in one check ๐Ÿ’€๐Ÿ”ฅ

The clang-analyzer-unix.Malloc check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.MallocSizeof

Checks for dubious malloc arguments involving sizeof. What do you mean you aren't sure what you just allocated ๐Ÿ˜ญ

The clang-analyzer-unix.MallocSizeof check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.MismatchedDeallocator

Checks for mismatched deallocators. If you allocated with one thing, free it with the matching thing, it's literally the bare minimum ๐ŸŽ€

The clang-analyzer-unix.MismatchedDeallocator check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.StdCLibraryFunctions

Checks for invalid arguments of C standard library functions, and applies relations between arguments and return value. Basically it knows what those functions promise and it will hold you to it ๐Ÿ‘‘

The clang-analyzer-unix.StdCLibraryFunctions check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.Stream

Checks stream handling functions. Open it, close it, check it โ€” the whole cycle โœจ

The clang-analyzer-unix.Stream check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.Vfork

Checks for proper usage of vfork. Same function, but now we're being proper about it ๐Ÿ’…

The clang-analyzer-unix.Vfork check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.cstring.BadSizeArg

Checks the size argument passed into C string functions for common erroneous patterns. That size argument is lying to you, bestie ๐Ÿซ 

The clang-analyzer-unix.cstring.BadSizeArg check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-unix.cstring.NullArg

Checks for null pointers being passed as arguments to C string functions. A null pointer walking into a string function is NOT the vibe ๐Ÿ˜ญ

The clang-analyzer-unix.cstring.NullArg check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-webkit.NoUncountedMemberChecker

Checks for no uncounted member variables. Exactly what it says on the tin, no notes ๐Ÿ’œ

The clang-analyzer-webkit.NoUncountedMemberChecker check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-webkit.RefCntblBaseVirtualDtor

Checks for any ref-countable base class having virtual destructor. If you're ref-countable, you will have that virtual destructor, periodt ๐ŸŽ€

The clang-analyzer-webkit.RefCntblBaseVirtualDtor check is an alias, please see Clang Static Analyzer Available Checkers for more information.

clang-analyzer-webkit.UncountedLambdaCapturesChecker

Checks uncounted lambda captures. Your lambda grabbed something and forgot to count it, iconic behavior honestly ๐Ÿ™ˆ

The clang-analyzer-webkit.UncountedLambdaCapturesChecker check is an alias, please see Clang Static Analyzer Available Checkers for more information.


concurrency-mt-unsafe

Checks for some thread-unsafe functions against a black list of known-to-be-unsafe functions. Usually they access static variables without synchronization (e.g. gmtime(3)) or utilize signals in a racy way. The set of functions to check is specified with the FunctionSet option. ๐Ÿ”ฅ

Yes bestie, there's a whole black list. You can customize which functions get checked with FunctionSet โ€” more on that coming up ๐Ÿ‘€

okay so quick but important note before we keep going: using some thread-unsafe functions may still be valid in concurrent programming if only a single thread is used (like setenv(3)) โ€” but bestie, some functions track a state in global variables, and that state would get clobbered by subsequent (non-parallel, but concurrent) calls to a related function. e.g. the following code literally 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, because we love a concrete moment ๐Ÿ“:

tm = gmtime(timep); // uses a global buffer

sleep(1); // implementation may use SIGALRM

โœจ Options โœจ

  • FunctionSet โ€” specifies which functions in libc should be considered thread-safe, possible values are posix, glibc, or any.
    • posix means POSIX defined thread-unsafe functions. POSIX.1-2001 in "2.9.1 Thread-Safety" defines that all functions specified in the standard are thread-safe except a predefined list of thread-unsafe functions.
    • Glibc defines some of them as thread-safe (e.g. dirname(3)), but adds non-POSIX thread-unsafe ones (e.g. getopt_long(3)). Glibc's list is compiled from GNU web documentation with a search for MT-Safe tag: https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html
    • If you want to identify thread-unsafe API for at least one libc or unsure which libc will be used, use any (default).

๐Ÿšซ concurrency-thread-canceltype-asynchronous

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 type PTHREAD_CANCEL_DEFERRED instead which is the default. Even with deferred cancellation, a cancellation point in 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. that's it, that's the whole vibe โœจ


๐ŸŒ€ cppcoreguidelines-avoid-capturing-lambda-coroutines

Flags C++20 coroutine lambdas with non-empty capture lists that may cause use-after-free errors and suggests avoiding captures or ensuring the lambda closure object has a guaranteed lifetime.

This check implements CP.51 from the C++ Core Guidelines. ๐Ÿ‘‘

Using coroutine lambdas with non-empty capture lists can be risky, as capturing variables can lead to accessing freed memory after the first suspension point. This issue can occur even with refcounted smart pointers and copyable types. When a lambda expression creates a coroutine, it results in a closure object with storage, which is often on the stack and will eventually go out of scope. When the closure object goes out of scope, its captures also go out of scope. While normal lambdas finish executing before this happens, coroutine lambdas may resume from suspension after the closure object has been destructed, resulting in use-after-free memory access for all captures. ๐Ÿ’€

Consider the following example:

int value = get_value();
std::shared_ptr<Foo> sharedFoo = get_foo();
{
    const auto lambda = [value, sharedFoo]() -> std::future<void>
    {
        co_await something();
        // "sharedFoo" and "value" have already been destroyed
        // the "shared" pointer didn't accomplish anything
    };
    lambda();
} // the lambda closure object has now gone out of scope

In this example, the lambda object is defined with two captures: value and sharedFoo. When lambda() is called, the lambda object is created on the stack, and the captures are copied into the closure object. When the coroutine is suspended, the lambda object goes out of scope, and the closure object is destroyed. When the coroutine is resumed, the captured variables may have been destroyed, resulting in use-after-free bugs. yeah. not the moment. ๐Ÿ™ˆ

In conclusion, the use of coroutine lambdas with non-empty capture lists can lead to use-after-free errors when resuming the coroutine after the closure object has been destroyed. This check helps prevent such errors by flagging C++20 coroutine lambdas with non-empty capture lists and suggesting avoiding captures or ensuring the lambda closure object has a guaranteed lifetime.

Following these guidelines can help ensure the safe and reliable use of coroutine lambdas in C++ code. slay ๐Ÿ’…

Options

  • AllowExplicitObjectParameters โ€” when set to true, lambda coroutines that use C++23 "deducing this" (explicit object parameter, e.g. this auto) are not flagged by this check, because the captures are moved into the coroutine frame, decoupling their lifetime from the lambda object.
    • Default is false.
    • The example from above can be made safe and will pass this check with the following change:
int value = get_value();
std::shared_ptr<Foo> sharedFoo = get_foo();
{
    // Pass "this auto" as the first argument to the lambda
    const auto lambda = [value, sharedFoo](this auto) -> std::future<void>
    {
        co_await something();
    };
    lambda();
} // the lambda closure object has now gone out of scope, but captures are
  // no longer coupled to its lifetime

๐ŸงŠ cppcoreguidelines-avoid-const-or-ref-data-members

This check warns when structs or classes that are copyable or movable, and have const-qualified or reference (lvalue or rvalue) data members. Having such members is rarely useful, and makes the class only copy-constructible but not copy-assignable. it's giving "why would you do this to yourself" ๐Ÿ˜ญ

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. They are less readable than plain while loops, since the termination condition is at the end and the condition is not checked prior to the first iteration. This can lead to subtle bugs. ๐Ÿ”ฅ

This check implements ES.75 from the C++ Core Guidelines.

Examples:

int x;
do {
    std::cin >> x;
    // ...
} while (x < 0);

Options

  • IgnoreMacros โ€” ignore the check when analyzing macros. This is useful for safely defining function-like macros:
#define FOO_BAR(x) \
do { \
  foo(x); \
  bar(x); \
} while(0)
  • Defaults to false.

๐Ÿƒโ€โ™€๏ธ cppcoreguidelines-avoid-goto

The usage of goto for control flow is error prone and should be replaced with looping constructs. Only forward jumps in nested loops are accepted. ๐Ÿ‘€

This check implements ES.76 from the C++ Core Guidelines.

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. no cap ๐Ÿงš

for(int i = 0; i < 100; ++i) {
  for(int j = 0; j < 100; ++j) {
    if (i * j > 500)
      goto early_exit;
  }
}

early_exit:
some_operation();

All other uses of goto are diagnosed in C++. ๐Ÿ’…

Options

  • IgnoreMacros โ€” if set to true, the check will not warn if a goto statement is expanded from a macro. Default is false.

๐Ÿ”ข 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. ๐Ÿซถ

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. every single one. iconic behaviour honestly ๐Ÿ’€

Options

  • AllowInternalLinkage โ€” when set to true, static non-const variables and variables in anonymous namespaces will not generate a warning. The default value is false.
  • AllowThreadLocal โ€” when set to true, non-const global variables with thread-local storage duration will not generate a warning. The default value is false.
  • IgnoreMacros โ€” when set to true, non-const global variables defined in macros will not generate a warning. The default value is false.

๐Ÿ”— cppcoreguidelines-avoid-reference-coroutine-parameters

Warns when a coroutine accepts reference parameters. After a coroutine suspend point, references could be dangling and no longer valid. Instead, pass parameters as values. ๐Ÿ˜ญ๐Ÿซถ

Examples:

          std::future<int> someCoroutine(int& val) {
            co_await ...;
            // When the coroutine is resumed, 'val' might no longer be valid.
            if (val) ...
          }

yeah bestie, that reference is giving dangling the second the coro gets resumed ๐Ÿ’€ This check is literally CP.53 from the C++ Core Guidelines, no cap.


cppcoreguidelines-c-copy-assignment-signature

Okay so this one is an alias ๐Ÿซถ โ€” go look at misc-unconventional-assign-operator for the actual tea.

cppcoreguidelines-explicit-constructor

Alias again, bestie โœจ โ€” this is an alias for misc-explicit-constructor.

cppcoreguidelines-explicit-virtual-functions

Another alias, iykyk ๐Ÿ˜Œ โ€” the cppcoreguidelines-explicit-virtual-functions check is an alias, so please see modernize-use-override for more info.

cppcoreguidelines-init-variables

This one checks whether there are local variables declared without an initial value ๐Ÿ˜ญ Which, like, can lead to unexpected behavior if there's some code path that reads the variable before assigning to it. Rude.

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 get checked ๐Ÿ‘‘ The fix option initializes all detected values with the value of zero. Exception though: float and double types get initialized to NaN instead, slay.

So like, a function that looks like this:

          void function() {
            int x;
            char *txt;
            double d;

            // Rest of the function.
          }

Would get rewritten to look like this:

          #include <math.h>

          void function() {
            int x = 0;
            char *txt = nullptr;
            double d = NAN;

            // Rest of the function.
          }

glow-up fr fr โœจ It also 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, llvm or google. Default is llvm ๐ŸŽ€
  • MathHeader โ€” a string specifying the header to include to get the definition of NAN. 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 ๐Ÿ˜ญ chaos honestly.

This check implements I.22 from the C++ Core Guidelines.

โš ๏ธ heads up bestie: note that currently this does not flag calls to non-constexpr functions, and therefore globals could still be accessed from functions themselves. So like, it's not catching everything, k?

cppcoreguidelines-macro-to-enum

Alias alert ๐Ÿ’… โ€” 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 ๐Ÿ™ˆ Like, we have nicer things now, bestie.

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__)
                  ^

four warnings, four whole vibes ๐Ÿ’€

Options

  • AllowedRegexp โ€” a regular expression to filter allowed macros ๐ŸŽ€ For example DEBUG*|LIBTORRENT*|TORRENT*|UNI* could be applied to filter libtorrent. Default value is ^DEBUG_*.
  • CheckCapsOnly โ€” boolean flag to warn on all macros except those with CAPS_ONLY names. This option is intended to ease introduction of this check into older code bases (be gentle with the legacy repo bestie ๐Ÿซถ). Default value is false.
  • IgnoreCommandLineMacros โ€” boolean flag to toggle ignoring command-line-defined macros. Default value is true.

cppcoreguidelines-misleading-capture-default-by-value

Warns when a lambda specifies a by-value capture default and captures this ๐Ÿ˜ญ deceptive behavior honestly.

Okay so the lore: by-value capture defaults in member functions can be misleading about whether data members are captured by value or reference. This happens because specifying the capture default [=] actually captures the this pointer by value โ€” not the data members themselves. So as a result, data members are still indirectly accessed via the captured this pointer, which essentially means they're being accessed by reference. So even when you're using [=], data members are effectively captured by reference, which might not align with what you expected ๐Ÿ™ˆ it's giving 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 ๐Ÿ˜ญ like bestie you had ONE job.

Example:

          template <class T>
          void wrapper(T&& t) {
            impl(std::forward<T>(t), 1, 2); // Correct
          }

          template <class T>
          void wrapper2(T&& t) {
            impl(t, 1, 2); // Oops - should use std::forward<T>(t)
          }

          template <class T>
          void wrapper3(T&& t) {
            impl(std::move(t), 1, 2); // Also buggy - should use std::forward<T>(t)
          }

          template <class F>
          void wrapper_function(F&& f) {
            std::forward<F>(f)(1, 2); // Correct
          }

          template <class F>
          void wrapper_function2(F&& f) {
            f(1, 2); // Incorrect - may not invoke the desired qualified function operator
          }

Options

  • ForwardFunction โ€” specify the function used for forwarding. Default is ::std::forward โœจ

This check implements F.19 from the C++ Core Guidelines ๐Ÿซถ

Limitations

Explicit object parameters (this Self&&) with a type constraint are not checked, to avoid false positives ๐Ÿ™ˆ Such parameters are rarely intended to be perfectly forwarded, so like, it's fine.

cppcoreguidelines-narrowing-conversions

This check implements part of ES.46 from the C++ Core Guidelines.

It's an alias bestie ๐Ÿ’… โ€” the cppcoreguidelines-narrowing-conversions check is an alias, please see bugprone-narrowing-conversions for more information.

cppcoreguidelines-no-malloc

This check handles C-Style memory management using malloc(), realloc(), calloc() and free() ๐Ÿ˜ญ It warns about its use and tries to suggest the use of an appropriate RAII object. And get this โ€” it can also be configured to check against a user-specified list of functions that are used for memory management (e.g. posix_memalign()) ๐Ÿ‘‘

This check implements R.10 from the C++ Core Guidelines.

โš ๏ธ real talk: there is no attempt made to provide fix-it hints, since manual resource management isn't easily transformed automatically into RAII. Soz bestie, you're doing this one by hand ๐Ÿ’…
          // Warns each of the following lines.
          // Containers like std::vector or std::string should be used.
          char* some_string = (char*) malloc(sizeof(char) * 20);
          char* some_string = (char*) realloc(sizeof(char) * 30);
          free(some_string);

          int* int_array = (int*) calloc(30, sizeof(int));

          // Rather use a smartpointer or stack variable.
          struct some_struct* s = (struct some_struct*) malloc(sizeof(struct some_struct));

Options

  • Allocations โ€” semicolon-separated list of fully qualified names of memory allocation functions. Defaults to ::malloc;::calloc ๐ŸŽ€
  • Deallocations โ€” semicolon-separated list of fully qualified names of memory allocation functions. Defaults to ::free โœจ
  • Reallocations โ€” semicolon-separated list of fully qualified names of memory allocation functions. Defaults to ::realloc ๐Ÿฆ‹

cppcoreguidelines-no-suspend-with-lock

Flags coroutines that suspend while a lock guard is in scope at the suspension point ๐Ÿ˜ญ this is the scary one.

When a coroutine suspends, any mutexes held by the coroutine will remain locked until the coroutine resumes and eventually destructs the lock guard. That can lead to long periods with a mutex held, and runs the risk of deadlock ๐Ÿ’€ Not the vibe.

Instead, locks should be released before suspending a coroutine ๐Ÿซถ

heads up: 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.

Alias, bestie ๐Ÿ’… โ€” 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.

Also an alias โœจ โ€” the cppcoreguidelines-noexcept-move-operations check is an alias, please see performance-noexcept-move-constructor for more information.

cppcoreguidelines-noexcept-swap

This check implements C.83, C.84 and C.85 from the C++ Core Guidelines ๐Ÿ‘‘ three whole rules, she ate.

And yeah โ€” it's an alias ๐ŸŽ€ the cppcoreguidelines-noexcept-swap check is an alias, please see performance-noexcept-swap for more information.

cppcoreguidelines-non-private-member-variables-in-classes</p>

Okay so bestie, this one is literally just an alias ๐Ÿ’… โ€” please see misc-non-private-member-variables-in-classes for the actual tea.


cppcoreguidelines-owning-memory

This check implements the type-based semantics of gsl::owner<T*>, which lets static analysis happen on code that uses raw pointers to handle resources like dynamic memory โ€” but won't introduce RAII concepts. No cap, it's just the vibes check on your pointer types โœจ

This check implements I.11, C.33, R.3 and GSL.Views from the C++ Core Guidelines. And the definition of a gsl::owner<T*> is straight up simple:

namespace gsl { template <typename T> owner = T; }

So introducing the owner is genuinely easy even without using an implementation of the Guideline Support Library ๐Ÿซถ

All checks are purely type based and not (yet) flow sensitive. Periodt.

The following examples 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 that 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 introduce gsl::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 introduce gsl::owner<>. Defaults to ::free;::realloc;::freopen;::fclose.

Limitations

Using gsl::owner<T*> in a typedef or alias is not handled correctly ๐Ÿ˜ญ

using heap_int = gsl::owner<int*>;
heap_int allocated = new int(42); // False positive!

The gsl::owner<T*> is declared as a templated type alias. In template functions and classes, like in the example below, the information of the type aliases gets lost. So using gsl::owner<T*> in a heavy templated code base might lead to false positives. Yeah. It's giving tragic, ikyk.

Known code constructs that do not get diagnosed correctly are:

  • std::exchange
  • std::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 the type based checking. Suppose you have code like the following:

// Two owners with assigned resources
gsl::owner<int*> Owner1 = new int(42);
gsl::owner<int*> Owner2 = new int(42);

Owner2 = Owner1; // Conceptual Leak of initial resource of Owner2!
Owner1 = nullptr;

The semantic of a gsl::owner<T*> is mostly like a std::unique_ptr<T>, therefore assignment of two gsl::owner<T*> is considered a move, which requires that the resource Owner2 must have been released before the assignment. This kind of condition could be caught in later improvements of this check with flowsensitive analysis. Currently, the Clang Static Analyzer catches this bug for dynamic memory, but not for general types of resources. ๐Ÿ’€


cppcoreguidelines-prefer-member-initializer

Finds member initializations in the constructor body which can be converted into member initializers of the constructor instead. This not only improves the readability of the code but 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.

Heads up bestie โ€” 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;
  }
};

Here n can be initialized in the constructor initializer list, unlike m, as m's initialization follows a control statement (if):

class C {
  int n;
  int m;
public:
  C(): n(1) {
    if (dice())
      return;
    m = 1;
  }
};

Example 2

class C {
  int n;
  int m;
public:
  C(int nn, int mm) {
    n = nn; // Neither default constructor nor literal
    if (dice())
      return;
    m = mm;
  }
};

Here n can be initialized in the constructor initializer list, unlike m, as m's initialization follows a control statement (if):

C(int nn, int mm) : n(nn) {
  if (dice())
    return;
  m = mm;
}

cppcoreguidelines-pro-bounds-array-to-pointer-decay

This check flags all array to pointer decays.

Pointers should not be used as arrays. span<T> is a bounds-checked, safe alternative to using pointers to access arrays. Slay ๐Ÿ’…

This rule is part of the Bounds safety (Bounds 3) profile from the C++ Core Guidelines.


cppcoreguidelines-pro-bounds-avoid-unchecked-container-access

Finds calls to operator[] in STL containers and suggests replacing them with safe alternatives. Safe alternatives include STL at or GSL at functions, begin() or end() functions, range-for loops, std::span, or an appropriate function from <algorithms>. Obsessed with how safe this is ngl ๐ŸŒธ

For example, both

std::vector<int> a;
int b = a[4];

and

std::unique_ptr<vector> a;
int b = a[0];

will generate a warning.

STL containers for which operator[] is well-defined for all inputs are excluded from this check (e.g.: std::map::operator[]).

This check enforces part of the SL.con.3 guideline and is part of the Bounds Safety (Bounds 4) profile from the C++ Core Guidelines.

Options

  • ExcludeClasses โ€” Semicolon-separated list of regular expressions matching class names that overwrites the default exclusion list. The default is: ::std::map;::std::unordered_map;::std::flat_map.
  • FixMode โ€” Determines what fixes are suggested. Either none, at (use a.at(index) if a fitting function exists) or function (use a function f(a, index)). The default is none.
  • FixFunction โ€” The function to use in the function mode. For C++23 and beyond, the passed function must support the empty subscript operator, i.e., the case where a[] becomes f(a). FixFunctionEmptyArgs can be used to override the suggested function in that case. The default is gsl::at.
  • FixFunctionEmptyArgs โ€” The function to use in the function mode for the empty subscript operator case in C++23 and beyond only. If no fixes should be made for empty subscript operators, pass an empty string. In that case, only the warnings will be printed. The default is the value of FixFunction.

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 hand you fixes after you set this to the name of the include file that contains gsl::at(), like "gsl/gsl.h". Default is an empty string, so it does nothing until you tell it bestie. ๐ŸŽ€
  • IncludeStyle โ€” a string telling it which include style you're on: llvm or google. Default is llvm. Slay, pick your aesthetic. ๐Ÿ’…

๐Ÿฆ‹ cppcoreguidelines-pro-bounds-pointer-arithmetic ๐Ÿฆ‹

Okay so this check flags, like, every usage of pointer arithmetic, because it could land you with an invalid pointer. Real "trust me bro" energy. Small mercy though: 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 โ€” no cap. span<T> is a bounds-checked, safe type for accessing arrays of data, so use that instead, bestie. โœจ

This rule is part of the Bounds safety (Bounds 1) profile from the C++ Core Guidelines.

Options

  • AllowIncrementDecrementOperators โ€” when enabled, the check will allow using the prefix/postfix increment or decrement operators on pointers. Default is false. ๐Ÿ“ˆ

๐Ÿ’œ cppcoreguidelines-pro-type-const-cast ๐Ÿ’œ

Imposes limitations on the use of const_cast within C++ code. It depends on the StrictMode option setting to determine whether it should flag all instances of const_cast or only those that remove either const or volatile qualifier.

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. It's giving "the rules are the rules."

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. Seriously, don't. ๐Ÿ’€

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 to true, it means that any usage of const_cast is not allowed. On the other hand, when it's set to false, it permits casting to const or volatile types. Default value is false. ๐Ÿ”ฅ

๐ŸŒธ 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. The audacity honestly. ๐Ÿ˜ญ 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 โ€” think builtins, pointers, and record types without user-provided default constructors containing at least one such type. If these fields aren't initialized, the constructor will leave some of the memory in an undefined state. ๐Ÿ™ˆ Not the vibe.

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. Iconic behavior honestly. ๐Ÿ‘‘

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. Slight overachiever moment, but okay.

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 to true, 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 is false. โšก
  • UseAssignment โ€” if set to true, the check will provide fix-its with literal initializers (int i = 0;) instead of curly braces (int i{};). Default is false. โœ๏ธ

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. Yeah. That's the whole warning. ๐Ÿšฉ

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. Free glow-up, love that for you. ๐Ÿ’–

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 to false, no warnings are emitted for casts on non-polymorphic types. Default is true. ๐Ÿซก

๐Ÿ“ cppcoreguidelines-pro-type-union-access ๐Ÿ“

This check flags all access to members of unions. Passing unions as a whole is not flagged โ€” okay, loophole noted. ๐Ÿ•ต๏ธ

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. It's literally vibes-based correctness, which is terrifying ngl. ๐Ÿ˜ญ

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 function is used in unevaluated context. Tiny exception, we respect it. ๐ŸŽ€

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. Bestie. You had one job.

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. We're not gonna bully you for an intentionally unused param, periodt. โœจ

Example:

void conditional_use([[maybe_unused]] std::string&& Input) {
  // No diagnostic here since Input is unused and marked as such
}

Options

  • AllowPartialMove โ€” if set to true, the check accepts std::move calls containing any subexpression containing the parameter. CppCoreGuideline F.18 officially mandates that the parameter itself must be moved. Default is false. ๐ŸŒ€
// '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);
}
  • AllowImplicitMove โ€” if set to true, the check recognizes implicit move return param; where param is a rvalue reference parameter. Default is false. ๐Ÿ’ซ
A f(A&& a) {
  return a; // no warning with AllowImplicitMove = true
}
  • IgnoreUnnamedParams โ€” if set to true, the check ignores unnamed rvalue reference parameters. Default is false. ๐Ÿคซ
  • IgnoreNonDeducedTemplateTypes โ€” if set to true, the check ignores non-deduced template type rvalue reference parameters. Default is false. ๐Ÿงฉ
template <class T>
struct SomeClass {
  // Below, 'T' is not deduced and 'T&&' is an rvalue reference type.
  // This will be flagged if and only if IgnoreNonDeducedTemplateTypes is
  // false. One suggested fix would be to specialize the class for 'T' and
  // 'T&' separately (e.g., see std::future), or allow only one of 'T' or
  // 'T&' instantiations of SomeClass (e.g., see std::optional).
  SomeClass(T&& t) { }
};

// Never flagged, since 'T' is a forwarding reference in a deduced context
template <class T>
void forwarding_ref(T&& t) {
  T other = std::forward<T>(t);
}
  • MoveFunction โ€” specify the function used for moving. Default is ::std::move. ๐Ÿšš

This check implements F.18 from the C++ Core Guidelines.


๐Ÿฆ‹ cppcoreguidelines-slicing ๐Ÿฆ‹

Flags slicing of member variables or vtable. Slicing happens when copying a derived object into a base object: the members of the derived object (both member variables and virtual member functions) will be discarded. Literally the derived part just falls off?? ๐Ÿ˜ญ This can be misleading especially for member function slicing, for example:

struct B { int a; virtual int f(); };
struct D : B { int b; int f() override; };

void use(B b) {  // Missing reference, intended?
  b.f();  // Calls B::f.
}

D d;
use(d);  // Slice.

This check implements ES.63 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. Half a personality is not a personality, bestie. ๐Ÿ’…

Okay so bestie, real talk: the compiler is out here giving you a copy constructor, a copy assignment operator, a move constructor, a move assignment operator and a destructor for free, by default. Iconic of it. But you can suppress any of those defaults by explicitly defining them yourself. The drama is that the relationship between which functions get suppressed when you define others is genuinely complicated, so the advice is: just default all five or explicitly define all five. Keep the friend group together, y'know? ๐Ÿซถ

Also, tiny but mighty detail: defining a function with = delete counts as a definition. So don't think you're slick.

This check implements C.21 from the C++ Core Guidelines. Slay.

๐ŸŽ€ Options ๐ŸŽ€

  • AllowSoleDefaultDtor โ€” when set to true (default is false), this check will only trigger on destructors if they're 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 to true (default is false), this check doesn't flag classes that define no move operations at all. It still flags classes that define only one of either move constructor or move assignment operator. The way this option works: with it enabled, the class below won't be flagged.
struct A {
  A(const A&);
  A& operator=(const A&);
  ~A();
};
  • AllowMissingMoveFunctionsWhenCopyIsDeleted โ€” when set to true (default is false), this check doesn't flag classes that 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, this class won't be flagged:
struct A {
  A(const A&) = delete;
  A& operator=(const A&) = delete;
  ~A();
};
  • AllowImplicitlyDeletedCopyOrMove โ€” when set to true (default is false), this check doesn't flag classes that implicitly delete copy or move operations. With this option enabled, this class won't be flagged:
struct A : boost::noncopyable {
  ~A() { std::cout << "dtor\n"; }
};
  • IgnoreMacros โ€” if set to true, the check won't give warnings for classes defined inside macros. Default is true.

๐Ÿฆ‹ cppcoreguidelines-use-default-member-init ๐Ÿฆ‹

This check implements C.48 from the C++ Core Guidelines. Periodt.

Heads up though: the cppcoreguidelines-use-default-member-init check is an alias โ€” please see modernize-use-default-member-init for more information. It's giving "I'm the same check but in a different outfit" ๐Ÿ’…


๐ŸŒธ cppcoreguidelines-use-enum-class ๐ŸŒธ

Finds unscoped (non-class) enum declarations and suggests using enum class instead. Basically it's the "bestie, put a class on that enum" check.

This check implements Enum.3 from the C++ Core Guidelines.

Example:

enum E {};        // use "enum class E {};" instead
enum class E {};  // OK

struct S {
    enum E {};    // use "enum class E {};" instead
                  // OK with option IgnoreUnscopedEnumsInClasses
};

namespace N {
    enum E {};    // use "enum class E {};" instead
}

โœจ Options โœจ

  • IgnoreUnscopedEnumsInClasses โ€” when true, ignores unscoped enum declarations in classes. Default is false.
  • IgnoreMacros โ€” when true, ignores unscoped enum declarations within macros. Default is false.

๐Ÿ‘‘ 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 โ€” and we do not play with undefined behavior, bestie, that's the main character villain of this whole story ๐Ÿ˜ญ

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. Why? Because 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. That one's your call, babe.

๐Ÿ’– 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. Not it. ๐Ÿ™ˆ

This check will detect following function invocations:

  • OSSpinlockLock
  • OSSpinlockTry
  • OSSpinlockUnlock

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. Yeah. Don't.

It's a common pattern to have functions initialize internal static or global data once when the function runs โ€” but programmers have been known to miss the static on the dispatch_once_t predicate, leading to an uninitialized flag value at the mercy of the stack. The stack, bestie. Imagine leaving your whole life in the hands of the stack ๐Ÿ˜ญ

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. Oops.

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. Different check, same energy.

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 misc-anonymous-namespace-in-header for more information.


๐Ÿ”ฅ fuchsia-multiple-inheritance ๐Ÿ”ฅ

The fuchsia-multiple-inheritance check is an alias โ€” please See misc-multiple-inheritance for details.

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 get a pass, they're cool.

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-temporary-objects ๐Ÿฆ‹

Warns on construction of specific temporary objects in the Zircon kernel. If the object should be flagged, the fully qualified type name must be explicitly passed to the check. No name on the list, no flag โ€” that's the whole vibe check.

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.

class Derived : Foo {} // Derived is not explicitly disallowed
Derived();             // and so temporary construction is okay

โœจ Options โœจ

  • Names โ€” a semi-colon-separated list of fully-qualified names of C++ classes that should not be constructed as temporaries. Default is empty string.

๐ŸŒธ fuchsia-trailing-return ๐ŸŒธ

Functions that have trailing returns are disallowed โ€” except for those using decltype specifiers and lambda with otherwise unutterable return types. Those get the VIP pass.

For example:

// No warning
int add_one(const int arg) { return arg; }

// Warning
auto get_add_one() -> int (*)(const int) {
  return add_one;
}

Exceptions are made for lambdas and decltype specifiers:

          // No warning
          auto lambda = [](double x, double y) -> double {return x + y;};

          // No warning
          template <typename T1, typename T2>
          auto fn(const T1 &lhs, const T2 &rhs) -> decltype(lhs + rhs) {
            return lhs + rhs;
          }

okay so both of those are completely fine, no warnings, we love a quiet compile ๐Ÿ˜Œโœจ bestie, if you wanna see the full list of stuff Fuchsia disallows, it's right here:

https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en

fuchsia-virtual-inheritance ๐ŸŒธ

Warns you if a class is defined with virtual inheritance. It's giving "we don't do that here."

So like, this is a big nope:

class B : public virtual A {};   // warning

Same Fuchsia link, because of course:

https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en

google-build-explicit-make-pair ๐ŸŽ€

Checks that make_pair's template arguments are deduced. ๐Ÿ’…

The lore: G++ 4.6 in C++11 mode fails badly if you spell out make_pair's template arguments explicitly โ€” and honestly it was never the vibe in the first place, like nobody intended that usage anyway.

The matching cpplint.py check name is: build/explicit_make_pair.

google-build-namespaces ๐ŸŒˆ

google-build-namespaces is an alias, so pls go see misc-anonymous-namespace-in-header for more info, bestie ๐Ÿซถ

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. Yeah. Those.

This check implements this rule from the Google C++ Style Guide: you may not use a using-directive to pull every name from a namespace into scope.

// 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. Periodt.

See https://google.github.io/styleguide/cppguide.html#Default_Arguments

google-explicit-constructor ๐Ÿ’œ

This check is an alias for misc-explicit-constructor.

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 โ€” that's it, that's the whole vibe check.

The relevant style guide section lives at https://google.github.io/styleguide/cppguide.html#Namespaces.

google-objc-avoid-nsobject-new ๐Ÿ’–

Finds calls to +new โ€” or overrides of it โ€” which are prohibited by the Google Objective-C style guide.

The Google Objective-C style guide forbids calling +new or overriding it in class implementations, and prefers +alloc and -init methods to instantiate objects instead.

An example, aka the forbidden snack:

NSDate *now = [NSDate new];
Foo *bar = [Foo new];

Instead, your 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. Not the move, bestie.

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 (do not do this):

- (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.

The corresponding style guide rule is right here: https://google.github.io/styleguide/objcguide.html#function-names

So: all function names should be in Pascal case. And functions whose storage class is not static should have an appropriate prefix. iykyk ๐Ÿ’…

This code sample is not following the 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 g[A-Z].* (for variables) or k[A-Z].* (for constants). And the check will actually suggest a variable name that follows the pattern โ€” if it can be inferred from the original name. Iconic behaviour, honestly.

For code:

static NSString* myString = @"hello";

The fix will be:

static NSString* gMyString = @"hello";

Another example, this time a constant:

static NSString* const myConstString = @"hello";

The fix will be:

static NSString* const kMyConstString = @"hello";

BUT โ€” for code prefixed with non-alphabetical characters, like:

static NSString* __anotherString = @"world";

the check will give you a warning message but will not be able to suggest a fix. You gotta fix it on your own, bestie. ๐Ÿ™ˆ

google-readability-avoid-underscore-in-googletest-name ๐Ÿ’€

Checks whether there are underscores in googletest test suite names and test names inside these test macros:

  • TEST
  • TEST_F
  • TEST_P
  • TYPED_TEST
  • TYPED_TEST_P

Note: the FRIEND_TEST macro is not included.

For example:

TEST(TestSuiteName, Illegal_TestName) {}
TEST(Illegal_TestSuiteName, TestName) {}

would trigger the check. Underscores are not allowed in test suite names nor test names. Periodt.

The DISABLED_ prefix โ€” which you might use 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. Sorry ๐Ÿซถ

google-readability-braces-around-statements ๐Ÿฆ‹

google-readability-braces-around-statements is an alias, please see readability-braces-around-statements for more information.

google-readability-casting โœจ

google-readability-casting is an alias, please see modernize-avoid-c-style-cast for more information.

It finds usages of C-style casts.

https://google.github.io/styleguide/cppguide.html#Casting

Corresponding cpplint.py check name: readability/casting.

google-readability-function-size ๐ŸŒธ

google-readability-function-size is an alias, please see readability-function-size for more information.

google-readability-namespace-comments ๐ŸŒˆ

google-readability-namespace-comments is an alias, please see llvm-namespace-comment for more information.

google-readability-todo ๐Ÿ’œ

Finds TODO comments without a username or bug number. Like, who left this here?? ๐Ÿ‘€

The relevant style guide section is https://google.github.io/styleguide/cppguide.html#TODO_Comments.

Corresponding cpplint.py check: readability/todo

Options โœจ

  • Style โ€” a string specifying the TODO style for fix-it hints. Accepted values are Hyphen and Parentheses. Default is Hyphen.
    • Hyphen will format the fix-it as: // TODO: username - details
    • Parentheses will format the fix-it as: // TODO(username): details

google-runtime-float ๐Ÿ“

Finds uses of long double and suggests against their use, because of a total lack of portability. It's giving "works on my machine" energy, ngl.

The corresponding style guide rule: https://google.github.io/styleguide/cppguide.html#Floating-Point_Types

google-runtime-int ๐Ÿ”ฅ

Finds uses of short, long and long long and suggests replacing them with u?intXX(_t)?.

The corresponding style guide rule: https://google.github.io/styleguide/cppguide.html#Integer_Types

Corresponding cpplint.py check: runtime/int

Options

  • UnsignedTypePrefix โ€” a string specifying the unsigned type prefix. Default is uint.
  • SignedTypePrefix โ€” a string specifying the signed type prefix. Default is int.
  • 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. Glow-up check, honestly ๐Ÿ’…

All names containing case are being replaced so they're consistent with the meanings of "test case" and "test suite" as used by the International Software Testing Qualifications Board and ISO 29119.

The new names are a part of Google Test version 1.9 (release pending). It's 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, and INSTANTIATE_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. And it tries to only give a warning when a fix can't be made safely โ€” like, say, with some template and macro uses. Safety first ๐Ÿซถ

linuxkernel-must-check-errs ๐Ÿง

Checks Linux kernel code to see if it uses the results from the functions in linux/err.h. It also checks whether code uses the results from functions that directly return a value from one of these error functions.

This is important in the Linux kernel because the return values of ERR_PTR, PTR_ERR, IS_ERR, IS_ERR_OR_NULL, ERR_CAST, and PTR_ERR_OR_ZERO 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 โ€” this check has your back ๐Ÿ’€๐Ÿ™

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 ๐ŸŒธ

Okay so llvm-else-after-return is literally just an alias, bestie โ€” it redirects you straight to readability-else-after-return for the real tea. That's the whole thing. Go read that one. ๐Ÿ’…

llvm-formatv-string โœจ

This one's a vibe check for your llvm::formatv format strings โ€” it compares them against the args you actually passed and calls out mismatched argument counts, unused arguments, and mixed index styles. Basically it's the friend who notices when you said you'd bring three snacks and showed up with one. ๐Ÿ“

Here's what it diagnoses:

  • The number of replacement indices in the format string does not match the number of arguments provided.
  • A format string does not use one of the given arguments.
  • Mixing of automatic and explicit indices (e.g. {} {1}).
// warning: format string requires 2 arguments, but 1 argument was provided
llvm::formatv("{0} {1}", x);

// warning: format string mixes automatic and explicit indices
llvm::formatv("{} {1}", x, y);

// warning: argument unused in format string
llvm::formatv("{0} {2}", x, y, z);

// OK.
llvm::formatv("{0} {1}", x, y);
llvm::formatv("{} {}", x, y);
llvm::formatv("{0} {0}", x);

Heads up though โ€” this check only operates on calls where the format string is a string literal. Dynamic format strings? Not diagnosed. Not today. ๐Ÿ™ˆ

Options

  • AdditionalFunctions โ€” a semicolon-separated list of extra fully qualified function names to check, beyond llvm::formatv and llvm::createStringErrorV. Every function you list has to be a variadic template whose last parameter is a parameter pack, and the format string is assumed to be the parameter immediately preceding that pack. So like, if you wanna check ::mylib::log(Level, const char *Fmt, Ts&&...), you'd set this option to ::mylib::log. Default is the empty string.

llvm-header-guard ๐Ÿ›ก๏ธ

Finds header guards that don't adhere to LLVM style, and fixes them. That's it, that's the whole check. Iconic minimalism. โœจ

llvm-include-order ๐Ÿ“š

Checks that your #includes are in the correct order. Very "alphabetize your bookshelf" of it, and honestly? Slay.

See https://llvm.org/docs/CodingStandards.html#include-style

llvm-namespace-comment ๐Ÿ’ฌ

Fun fact: google-readability-namespace-comments redirects here as an alias for this check. Two checks, one vibe.

It checks that long namespaces actually have a closing comment. Like, label your braces, bestie. ๐ŸŽ€

https://llvm.org/docs/CodingStandards.html#namespace-indentation

https://google.github.io/styleguide/cppguide.html#Namespaces

namespace n1 {
void f();
}

// becomes

namespace n1 {
void f();
}  // namespace n1

Options

  • ShortNamespaceLines โ€” requires the closing brace of the namespace definition to be followed by a closing comment if the body of the namespace has more than ShortNamespaceLines lines of code. It's an unsigned integer, defaults to 1U.
  • SpacesBeforeComments โ€” an unsigned integer specifying how many spaces go before the comment that closes a namespace definition. Default is 1U.
  • AllowOmittingNamespaceComments โ€” when true, the check will accept it if no namespace comment is present at all. It only fails if the namespace comment you did write is different than expected. Default is false.

llvm-prefer-isa-or-dyn-cast-in-conditionals ๐Ÿ”

This one peers into your conditionals and finds/replaces the sketchy stuff: cases of cast<> (which will assert rather than return a null pointer ๐Ÿ˜ญ) and dyn_cast<> where the return value isn't captured. It also catches the pattern var && isa<X>(var), where var gets evaluated twice โ€” not cute, fix it.

// 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 the historical habit of using unsigned to hold vregs and physregs, and rewrites them to use Register. Very "we don't do that anymore, we've grown" energy.

Right now it works by finding all variables of unsigned integer type whose initializer begins with an implicit cast from Register to unsigned.

void example(MachineOperand &MO) {
  unsigned Reg = MO.getReg();
  ...
}

becomes:

void example(MachineOperand &MO) {
  Register Reg = MO.getReg();
  ...
}

llvm-prefer-static-over-anonymous-namespace ๐Ÿซถ

Finds function and variable declarations sitting inside an anonymous namespace and suggests replacing them with static declarations.

Why? Because the LLVM Coding Standards recommend keeping anonymous namespaces as small as possible, and only using them for class declarations. For functions and variables, the static specifier should be preferred for restricting visibility. Periodt. ๐Ÿ’…

Example of non-compliant code:

namespace {

class StringSort {
public:
  StringSort(...)
  bool operator<(const char *RHS) const;
};

// warning: place method definition outside of an anonymous namespace
bool StringSort::operator<(const char *RHS) const {}

// warning: prefer using 'static' for restricting visibility
void runHelper() {}

// warning: prefer using 'static' for restricting visibility
int myVariable = 42;

}

Should become:

// Small anonymous namespace for class declaration
namespace {

class StringSort {
public:
  StringSort(...)
  bool operator<(const char *RHS) const;
};

}

// placed method definition outside of the anonymous namespace
bool StringSort::operator<(const char *RHS) const {}

// used 'static' instead of an anonymous namespace
static void runHelper() {}

// used 'static' instead of an anonymous namespace
static int myVariable = 42;

Options

  • AllowVariableDeclarations โ€” when true, allow variable declarations to be in anonymous namespace. Default value is true.
  • AllowMemberFunctionsInClass โ€” when true, only methods defined in an anonymous namespace outside of the corresponding class will be warned about. Default value is true.

llvm-qualified-auto ๐ŸŽ€

Alias alert bestie โ€” llvm-qualified-auto is an alias, please see readability-qualified-auto for more information.

llvm-redundant-casting ๐Ÿ’€

Points out uses of cast<>, dyn_cast<>, isa<> and their _or_null variants that are straight-up unnecessary, because the argument already is the target type, or a derived type thereof. Redundant. Embarrassing. We've all been there. ๐Ÿ™ˆ

struct A {};
A a;
// Finds:
A x = cast<A>(a);
// replaced by:
A x = a;

struct B : public A {};
B b;
// Finds:
A y = cast<A>(b);
// replaced by:
A y = b;

struct C : public A {};
C c;
// Finds:
bool r1 = isa<A>(a)    // always true
bool r2 = isa<A>(b)    // always true
bool r3 = isa<B, C>(c) // always true

Supported functions:

  • llvm::cast
  • llvm::cast_or_null
  • llvm::cast_if_present
  • llvm::dyn_cast
  • llvm::dyn_cast_or_null
  • llvm::dyn_cast_if_present
  • llvm::isa
  • llvm::isa_and_nonnull
  • llvm::isa_and_present

llvm-twine-local ๐Ÿงต

Looks for local Twine variables, which are prone to use-after-frees and should generally be avoided. Genuinely dangerous โ€” this is a real one, not a style nitpick. ๐Ÿ˜ญ

static Twine Moo = Twine("bark") + "bah";

// becomes

static std::string Moo = (Twine("bark") + "bah").str();

The Twine does not own the memory of its contents, so it is not recommended to use a Twine created from temporary strings or string literals. Like, at all.

static Twine getModuleIdentifier(StringRef moduleName) {
  return moduleName + "_module";
}
void foo() {
  Twine result = getModuleIdentifier(std::string{"abc"} + "def");
  // temporary std::string is destroyed here, result is dangling
}

So after applying these fix-it hints, the code will use std::string instead of Twine for local variables. But โ€” and this is important โ€” Twine has lots of methods that are incompatible with std::string, so you may need to adjust the code manually after applying the fix-it hints. The check does the first draft, you do the edit pass. ๐Ÿซถ

llvm-type-switch-case-types ๐ŸŒˆ

Finds llvm::TypeSwitch::Case calls with redundant explicit template arguments that can be inferred from the lambda parameter type. Sneaky-little-redundancy check.

It identifies two patterns:

  1. Redundant explicit type: when the lambda parameter type matches the Case template argument, the explicit type can be removed.
  2. Auto parameter with explicit type: when a lambda uses auto but Case has an explicit template argument, it suggests using an explicit type in the lambda instead.

Example

llvm::TypeSwitch<Base *, int>(base)
    .Case<DerivedA>([](DerivedA *a) { return 1; }) // Redundant.
    .Case<DerivedB>([](auto b) { return 2; });     // `auto` with explicit type.

Transforms to:

llvm::TypeSwitch<Base *, int>(base)
    .Case([](DerivedA *a) { return 1; })       // Type inferred from lambda.
    .Case<DerivedB>([](auto b) { return 2; }); // Warning only.
Note: the second case (auto parameter) only emits a warning without a fix-it, because the deduced type of auto depends on dyn_cast behavior, which varies between pointer types and MLIR handle types. Can't fix what we can't safely guess, bestie.

llvm-mlir-op-builder ๐Ÿ—๏ธ

Checks for uses of MLIR's old / to-be-deprecated OpBuilder::create<T> form and suggests using T::create instead. Out with the old, in with the new. โœจ

Example

builder.create<FooOp>(builder.getUnknownLoc(), "baz");

Transforms to:

FooOp::create(builder, builder.getUnknownLoc(), "baz");

llvm-use-ranges โญ

Finds calls to STL library iterator algorithms that could be replaced with LLVM range-based algorithms from llvm/ADT/STLExtras.h. Glow-up check: same logic, less boilerplate. ๐Ÿ’…

Example

auto it = std::find(vec.begin(), vec.end(), value);
bool all = std::all_of(vec.begin(), vec.end(),
                       [](int x) { return x > 0; });

Transforms to:

auto it = llvm::find(vec, value);
bool all = llvm::all_of(vec, [](int x) { return x > 0; });

Supported algorithms

Calls to the following STL algorithms are checked:

  • std::accumulate
  • std::adjacent_find
  • std::all_of
  • std::any_of
  • std::binary_search
  • std::copy_if
  • std::copy
  • std::count_if
  • std::count
  • std::equal
  • std::fill
  • std::find_if_not
  • std::find_if
  • std::find
  • std::for_each
  • std::includes
  • std::is_sorted
  • std::lower_bound
  • std::max_element
  • std::min_element
  • std::mismatch
  • std::none_of
  • std::partition_point
  • std::partition
  • std::remove_if
  • std::replace_copy_if
  • std::replace_copy
  • std::replace
  • std::search
  • std::stable_sort
  • std::transform
  • std::uninitialized_copy
  • std::unique
  • std::upper_bound

And bestie, when it applies the fix it drops in the #include you need for you, like a little gift ๐ŸŽ โ€” the check will add the necessary #include "llvm/ADT/STLExtras.h" directive when applying fixes.


๐Ÿฆ‹ llvm-use-vector-utils ๐Ÿฆ‹

Okay so this one is literally the "you're doing too much" check ๐Ÿ’…. It finds calls to llvm::to_vector with llvm::map_range or llvm::make_filter_range that can be swapped out for the way more concise llvm::map_to_vector and llvm::filter_to_vector utilities from llvm/ADT/SmallVectorExtras.h. Shorter, cuter, same energy. Obsessed.

Same deal as before โ€” the check will add the necessary #include "llvm/ADT/SmallVectorExtras.h" directive when applying fixes. ๐Ÿซถ

โœจ Example โœจ

auto v1 = llvm::to_vector(llvm::map_range(container, func));
auto v2 = llvm::to_vector(llvm::make_filter_range(container, pred));
auto v3 = llvm::to_vector<4>(llvm::map_range(container, func));
auto v4 = llvm::to_vector<4>(llvm::make_filter_range(container, pred));

Transforms to:

auto v1 = llvm::map_to_vector(container, func);
auto v2 = llvm::filter_to_vector(container, pred);
auto v3 = llvm::map_to_vector<4>(container, func);
auto v4 = llvm::filter_to_vector<4>(container, pred);

Look at that glow-up ๐Ÿ˜ญโœจ


๐Ÿ’œ llvmlibc-callee-namespace ๐Ÿ’œ

This one checks that all your calls resolve to functions within the correct namespace. Namespace discipline is self-care, fr fr.

// 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

See how ::strlen("!") gets the ick? Yeah. Global namespace? Not on her watch. ๐Ÿ’€


๐ŸŽ€ llvmlibc-implementation-in-namespace ๐ŸŽ€

Checks that all declarations in the llvm-libc implementation are within the correct namespace. Like, stay in your lane, but make it architecture. โœจ

// 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 {
    LLVM_LIBC_FUNCTION(char *, 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.
LLVM_LIBC_FUNCTION(char *, strcpy, (char *dest, const char *src)) {}

// Incorrect: outer most namespace is not the LIBC_NAMESPACE_DECL macro.
namespace something_else {
    LLVM_LIBC_FUNCTION(char *, 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 {
    LLVM_LIBC_FUNCTION(char *, strcpy, (char *dest, const char *src)) {}
}

Three flavors of "nope" right there ๐Ÿšฉ


๐ŸŒธ llvmlibc-inline-function-decl ๐ŸŒธ

Checks that all implicitly and explicitly inline functions in header files are tagged with the LIBC_INLINE macro โ€” the only exceptions being functions implicit to classes, or deleted functions. For more info about this macro, go peek at the libc style guide, iykyk ๐Ÿงš


๐ŸŒˆ llvmlibc-restrict-system-libc-headers ๐ŸŒˆ

Finds includes of system libc headers not provided by the compiler, hiding out inside llvm-libc implementations. Basically a "who let you in" check ๐Ÿšช

#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.
And why do we even care? Because this check is literally necessary โ€” accidentally including system libc headers can lead to subtle and hard to detect bugs ๐Ÿ˜ญ For example, consider a system libc whose dirent struct has slightly different field ordering than llvm-libc. While this will compile successfully, this can cause issues during runtime because they are ABI incompatible. It compiles! It runs! It lies to you! ๐Ÿ’€

๐ŸŒŸ Options ๐ŸŒŸ

  • Includes โ€” a string containing a comma separated glob list of allowed include filenames. Similar to the -checks glob 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 such as Linux development headers. See portability-restrict-system-includes for more details. ๐Ÿ‘€


๐Ÿ“ misc-anonymous-namespace-in-header ๐Ÿ“

Finds anonymous namespaces in headers. That's it. That's the vibe check.

And why are they a problem? Because anonymous namespaces in headers can lead to One Definition Rule (ODR) violations โ€” each translation unit including the header gets its own unique version of the symbols ๐Ÿ˜ญ This increases binary size and can cause confusing link-time errors. Not iconic. Not slay. ๐Ÿšซ

๐Ÿ’… References ๐Ÿ’…

This check corresponds to the CERT C++ Coding Standard rule DCL59-CPP. Do not define an unnamed namespace in a header file.

Corresponding cpplint.py check name: build/namespaces.


๐Ÿ”ฅ misc-confusable-identifiers ๐Ÿ”ฅ

Warns you about confusable identifiers โ€” aka identifiers that are visually close to each other but use different Unicode characters. It straight up detects a potential attack described in CVE-2021-42574. Spooky season but make it source code ๐Ÿ‘ป

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).

Bestie those are NOT the same letter and your compiler knows it ๐Ÿ˜ญ๐Ÿ’€


๐Ÿ‘‘ misc-const-correctness ๐Ÿ‘‘

Finds local variables and function parameters which could be declared as const but are not. It's the "you forgot your crown" check ๐Ÿ‘‘

Declaring variables as const is required or recommended by many coding guidelines, such as: ES.25 from the C++ Core Guidelines. So like, this isn't me being extra, this is written down ๐Ÿ“œ

Heads up though: please note that this check's analysis is type-based only. Variables that are not modified but used to create a non-const handle that might escape the scope are not diagnosed as potential const. Very much a "read the fine print" moment ๐Ÿ™ˆ
// Declare a variable, which is not ``const`` ...
int i = 42;
// but use it as read-only. This means that `i` can be declared ``const``.
int result = i * i;       // Before transformation
int const result = i * i; // After transformation

The check can analyze values, pointers and references and pointees:

// Normal values like built-ins or objects.
int potential_const_int = 42;       // Before transformation
int const potential_const_int = 42; // After transformation
int copy_of_value = potential_const_int;

MyClass could_be_const;       // Before transformation
MyClass const could_be_const; // After transformation
could_be_const.const_qualified_method();

// References can be declared const as well.
int &reference_value = potential_const_int;       // Before transformation
int const& reference_value = potential_const_int; // After transformation
int another_copy = reference_value;

// The similar semantics of pointers are analyzed.
int *pointer_variable = &potential_const_int; // Before transformation
int const*const pointer_variable = &potential_const_int; // After transformation, both pointer itself and pointee are supported.
int last_copy = *pointer_variable;

And the automatic code transformation is only applied to variables that are declared in single declarations. So you may want to prep your code base with readability-isolate-declaration first โ€” do the split, then get the consts ๐Ÿช„

Also note: there's the check cppcoreguidelines-avoid-non-const-global-variables to enforce const correctness on all globals. Different check, different jurisdiction ๐Ÿ’…

๐Ÿฆ‹ Limitations ๐Ÿฆ‹

  • The check does not run on C code. C girlies, this one's not for you ๐Ÿ™ˆ
  • The check will not analyze templated variables, template functions or variables that are instantiation dependent. Different instantiations can result in different const correctness properties and in general it is not possible to find all instantiations of a template. The template might be used differently in an independent translation unit. Translation units out here living their own lives, smh ๐Ÿ˜ญ

โœจ Options โœจ

  • AnalyzeValues โ€” enable or disable the analysis of ordinary value variables, like int i = 42;. Default is true.
// 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 โ€” enable or disable the analysis of reference variables, like int &ref = i;. Default is true.
int i = 42;
// Warning
int& ref = i;
// No warning
int const& ref = i;
  • AnalyzePointers โ€” enable or disable the analysis of pointers variables, like int *ptr = &i;. For specific checks, see WarnPointersAsValues and WarnPointersAsPointers. Default is true.
  • AnalyzeAutoVariables โ€” enable or disable the analysis of variables declared with auto, such as auto i = 10; or auto *ptr = &i. Default is true.
  • AnalyzeLambdas โ€” enable or disable the analysis of lambda variables, like auto f = [] { return 10; };. For this option to have any effect, AnalyzeAutoVariables must be true as well. Default is true. (Dependency chain, very main character ๐ŸŽ€)
  • AnalyzeParameters โ€” enable or disable the analysis of function parameters, like void foo(int* ptr). Only reference and pointer parameters are analyzed. Unnamed parameters, member functions (including constructors) and lambdas are excluded from the analysis. Default is true.
// Warning
void function(int& param) {}
// No warning
void function(const int& param, int&) {}
  • WarnPointersAsValues โ€” this option enables the suggestion for const of the pointer itself. Pointer values have two possibilities to be const: the pointer, and the value pointing to. Default is false.
int value = 42;

// Warning
const int * pointer_variable = &value;
// No warning
const int *const pointer_variable = &value;
  • WarnPointersAsPointers โ€” this option enables the suggestion for const of the value pointing to. Default is true.

Requires AnalyzePointers to be true. (No skipping the prerequisite, bestie ๐Ÿšฆ)

int value = 42;
// No warning
const int *const pointer_variable = &value;
// Warning
int *const pointer_variable = &value;
  • TransformValues โ€” gives you fixit-hints for value types that just automatically slap a const on there when it's a single declaration. Default is true. ๐ŸŽ€
// 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 โ€” same energy but for reference types: fixit-hints that auto-add const when it's a single declaration. Default is true. โœจ
// 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 โ€” fixit-hints for pointers if their pointee isn't changed. Default is false. ๐Ÿฆ‹
โš ๏ธ bestie, listen: this does not analyze whether the value-pointed-to is unchanged! Do not come for this check when it misses that. ๐Ÿ˜ญ

It requires WarnPointersAsValues to be true.

int value = 42;

// Before
const int * pointer_variable = &value;
// After
const int *const pointer_variable = &value;

// Before
const int * a[] = {&value, &value};
// After
const int *const a[] = {&value, &value};

// Before
int *ptr_value = &value;
// After
int *const ptr_value = &value;

int result = 100 * (*ptr_value); // Does not modify the pointer itself.
// This modification of the pointee is still allowed and not diagnosed.
*ptr_value = 0;

// The following pointer may not become a 'int *const'.
int *changing_pointee = &value;
changing_pointee = &result;
  • TransformPointersAsPointers โ€” fix-it hints for pointers when the value it's pointing to isn't changed. Default is false. ๐ŸŒธ

It requires WarnPointersAsPointers to be true.

int value = 42;

// Before
int * pointer_variable = &value;
// After
const int * pointer_variable = &value;

// Before
int * a[] = {&value, &value};
// After
const int * a[] = {&value, &value};
  • AllowedTypes โ€” a semicolon-separated list of type names that get excluded from const-correctness checking entirely, no questions asked. Regular expressions are accepted โ€” like, [Rr]ef(erence)?$ matches every type with the suffix Ref, ref, Reference and reference. ๐Ÿช„ If a name in the list contains the sequence ::, it's matched against the qualified type name (i.e. namespace::Type); otherwise it's matched against just the type name (i.e. Type). Default is an empty string.

๐ŸŒˆ misc-coroutine-hostile-raii ๐ŸŒˆ

Detects when objects of certain hostile RAII types persist across suspension points in a coroutine. The hostile squad includes scoped-lockable types and types that belong to a configurable denylist. ๐Ÿ’€

So here's the lore: some objects require that they be destroyed on the same thread that created them. Traditionally that requirement was phrased as "must be a local variable", because everyone assumed local variables always work that way. But that assumption is incorrect with C++20 coroutines โ€” an intervening co_await can cause the coroutine to suspend and then get resumed on a whole different thread. ๐Ÿ™ˆ

The lifetime of an object that needs to be destroyed on the same thread must not encompass a co_await or co_yield point. If you create/destroy an object, you gotta do it without letting the coroutine suspend in the meantime. Periodt.

These types are considered hostile:

  • Scoped-lockable types: a scoped-lockable object persisting across a suspension point is a problem because the lock it holds could get unlocked by a different thread. That's undefined behaviour, babe. ๐Ÿ˜ญ This includes all types annotated with the scoped_lockable attribute.
  • 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 that should not be allowed to persist across suspension points. Like: my::lockable;a::b;::my::other::lockable โ€” you get it. The default value of this option is std::lock_guard;std::scoped_lock. ๐Ÿ‘‘
  • AllowedAwaitablesList โ€” a semicolon-separated list of qualified types of awaitable types that are totally safe to await 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 in this list. RAII objects persisting across such a co_await expression are considered safe, so they don't get flagged. Slay.

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 โ€” default is an empty string.

  • AllowedCallees โ€” a semicolon-separated list of callee function names that can be safely awaited while hostile RAII objects are in scope. ๐Ÿงš

Example usage:

// Consider option AllowedCallees = "noop"
task noop() { co_return; }

task coro() {
  // This persists across the co_await but is not flagged
  // because the awaitable is considered safe to await on.
  const std::lock_guard l(&mu_);
  co_await noop();
}

Eg: my::safe::await;other::await โ€” default is an empty string.


๐ŸŽ€ misc-definitions-in-headers ๐ŸŽ€

Finds non-extern, non-inline function and variable definitions sitting in header files โ€” which is giving potential ODR violations the moment those headers get included from multiple translation units. Not cute. ๐Ÿ’…

// 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 serves up fixes that automatically add the inline keyword to discovered functions. ๐Ÿช„ Pleeease note: adding inline to variables is not currently supported by this check. Sorry bestie. ๐Ÿ˜ญ


๐ŸŒŸ misc-explicit-constructor ๐ŸŒŸ

Checks that constructors callable with a single argument โ€” and conversion operators โ€” are marked explicit, so you don't get got by unintentional implicit conversions. ๐Ÿ”ฅ

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 get implicitly converted to bool before comparison โ€” which is, like, so unlikely to be the intent. ๐Ÿ’€

The check will suggest inserting explicit before the constructor or conversion operator declaration. But โ€” copy and move constructors should not be explicit, and neither should constructors taking a single initializer_list argument. ๐Ÿ™…โ€โ™€๏ธ

This code:

struct S {
  S(int a);
  explicit S(const S&);
  operator bool() const;
  ...

will become

struct S {
  explicit S(int a);
  S(const S&);
  explicit operator bool() const;
  ...

๐Ÿ’… misc-header-include-cycle ๐Ÿ’…

This check detects cyclic #include dependencies between user-defined headers. Like, header A pulls in B, B pulls in C, C pulls in A โ€” bestie, thatโ€™s a whole loop and itโ€™s not slaying.

// 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 because 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.

To address these issues, a check has been developed to detect cyclic dependencies between header files, also known as โ€œ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. This 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. This can lead to faster compile times, improved code quality, and a more maintainable codebase overall. Additionally, by ensuring that header files are organized in a way that avoids cyclic dependencies, developers can make their code easier to understand and modify over time.

Itโ€™s worth noting that only user-defined headers and their dependencies are analyzed โ€” system includes such as standard library headers and third-party library headers are excluded. System includes are usually well-designed and free of include cycles, and ignoring them helps to focus on potential issues within the projectโ€™s own codebase. This limitation doesnโ€™t diminish the ability to detect #include cycles within the analyzed code.

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 โ€” provides a way to exclude specific files/headers from the warnings raised by a check. This can be achieved by specifying a semicolon-separated list of regular expressions or filenames. This option can be used as an alternative to //NOLINT when 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. 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"

โœจ Options โœจ

  • IgnoreHeaders โ€” a semicolon-separated list of regexes to disable insertion/removal of header files that match this regex as a suffix. E.g., foo/.* disables insertion/removal for all headers under the directory foo. Default is an empty string, no headers will be ignored.
  • DeduplicateFindings โ€” a boolean that controls whether the check should deduplicate findings for the same symbol. Defaults to true.
  • UnusedIncludes โ€” a boolean that controls whether the check should report unused includes (includes that are not used directly). Defaults to true.
  • MissingIncludes โ€” a boolean that controls whether the check should report missing includes (header files from which symbols are used but which are not directly included). Defaults to true.

๐ŸŒธ misc-misleading-bidirectional ๐ŸŒธ

Warns about unterminated bidirectional unicode sequence, detecting potential attack as described in the Trojan Source attack.

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 ๐ŸŽ€

Finds identifiers that contain Unicode characters with right-to-left direction, which can be confusing as they may change the understanding of a whole statement line, as described in Trojan Source.

An example of such misleading code follows:

#include <stdio.h>

short int ื = (short int)0;
short int ื’ = (short int)12345;

int main() {
  int ื = ื’; // a local variable, set to zero?
  printf("ื’ is %d\n", ื’);
  printf("ื is %d\n", ื);
}

๐Ÿ’œ misc-misplaced-const ๐Ÿ’œ

This check diagnoses when a const qualifier is applied to a typedef/using to a pointer type rather than to the pointee, because such constructs are often misleading to developers because the const applies to the pointer rather than the pointee.

For instance, in the following code, the resulting type is int * const rather than const int *:

typedef int *int_ptr;
void f(const int_ptr ptr) {
  *ptr = 0; // potentially quite unexpectedly the int can be modified here
  ptr = 0; // does not compile
}

The check does not diagnose when the underlying typedef/using type is a pointer to a const type or a function pointer type. This is because the const qualifier is less likely to be mistaken because it would be redundant (or disallowed) on the underlying pointee type.


๐ŸŒˆ misc-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; }
};

โญ misc-new-delete-overloads โญ

cert-dcl54-cpp redirects here as an alias for this check.

The check flags overloaded operator new() and operator delete() functions that do not have a corresponding free store function defined within the same scope. For instance, the check will flag a class implementation of a non-placement operator new() when the class does not also define a non-placement operator delete() function as well.

The check does not flag implicitly-defined operators, deleted or private operators, or placement operators.

This check corresponds to CERT C++ Coding Standard rule DCL54-CPP. Overload allocation and deallocation functions as a pair in the same scope.


๐Ÿ”ฅ misc-no-recursion ๐Ÿ”ฅ

Finds strongly connected functions (by analyzing the call graph for SCCโ€™s (Strongly Connected Components) that are loops), diagnoses each function in the cycle, and displays one example of a possible call graph loop (recursion).

๐ŸŽ€ 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.

Flags dereferences and non-pointer declarations of objects that are not meant to be passed by value, such as C FILE objects or POSIX pthread_mutex_t objects.

๐ŸŽ€ References ๐ŸŽ€

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 contain non-static data members in addition to user-declared non-static member functions and diagnose all data members declared with a non-public access specifier. The data members should be declared as private and accessed through member functions instead of exposed to derived classes or class consumers.

โœจ Options โœจ

  • IgnoreClassesWithAllMemberVariablesBeingPublic โ€” when true, allows to completely ignore classes if all the member variables in that class declared with a public access specifier. Default is false.
  • IgnorePublicMemberVariables โ€” when true, allows to ignore (not diagnose) all the member variables declared with a public access specifier. Default is false.

๐Ÿซถ misc-override-with-different-visibility ๐Ÿซถ

Finds virtual function overrides with different visibility than the function in the base class. This includes for example if a virtual function declared as private is overridden and declared as public in a subclass. The detected change is the modification of visibility resulting from keywords public, protected, private at overridden virtual functions. The check applies to any normal virtual function and optionally to destructors or operators. Use of the using keyword is not considered as visibility change by this check.

class A {
public:
  virtual void f_pub();
private:
  virtual void f_priv();
};

class B: public A {
public:
  void f_priv(); // warning: changed visibility from private to public
private:
  void f_pub(); // warning: changed visibility from public to private
};
class C: private A {
  // no warning: f_pub becomes private in this case but this is from the
  // private inheritance
};

class D: private A {
public:
  void f_pub(); // warning: changed visibility from private to public
                // 'f_pub' would have private access but is forced to be
                // public
};

okay so if the visibility flips like that, it's a big yikes โ€” it can legitimately mean bad design or, like, an actual programming error bestie, that's the whole point of flagging it ๐Ÿ’…

and here's the tea on virtual functions: if a virtual function is private in a subclass but public in the base class, you can still get at it from a pointer to the subclass if you convert that pointer to the base type ๐Ÿ˜ญ so honestly? private inheritance can probably be used instead, just saying.

a protected virtual function that gets made public in a subclass might have valid use cases, sure, but you can achieve a similar (not exactly the same, don't come for me) effect with the using keyword โœจ

โš™๏ธ Options

  • DisallowedVisibilityChange โ€” controls what kind of visibility change the check actually detects. Possible values are any, widening, narrowing. Like, widening will only warn if the visibility goes from more restrictive (private) to less restrictive (public). Default value is any ๐Ÿ‘‘
  • CheckDestructors โ€” if true, the check applies to destructors too. Otherwise destructors get ignored entirely. Default value is false ๐ŸŽ€
  • CheckOperators โ€” if true, the check also applies to overloaded C++ operators (as virtual member functions). This includes other special member functions (like conversions) too. Honestly this option is probably only useful in rare cases since operators and conversions aren't often virtual functions. Default value is false ๐Ÿฆ‹
  • IgnoredFunctions โ€” use this to skip the check at specific functions. To configure it, hand over a semicolon-separated list of function names. The list can contain regular expressions, so you can select all functions of a specific class (like MyClass::.*) or a specific function of any class (like my_function or ::.*::my_function). The function names are matched at the base class. Default value is empty string ๐Ÿซถ

๐ŸŒธ misc-predictable-rand

Warns you when you use std::rand(). bestie, pseudorandom number generators use mathematical algorithms to produce a sequence of numbers with good statistical properties, but the numbers produced are not genuinely random โ€” they're just doing math at you ๐Ÿ’€ std::rand() takes a seed (a number), runs a mathematical operation on it, and returns the result. By manipulating the seed, the result can be predicted. not it. ๐Ÿ™ˆ

๐Ÿ“š References

This check corresponds to the CERT C Coding Standard rules MSC30-C. Do not use the rand() function for generating pseudorandom numbers. MSC50-CPP. Do not use std::rand() for generating pseudorandom numbers.


๐Ÿงš misc-redundant-expression

Detects redundant expressions, which are typically errors from copy-paste (we've all been there, no shame, but also โ€” fix it).

Depending on the operator, expressions may be:

  • redundant,
  • always true,
  • always false,
  • always a constant (zero or one).

Examples:

((x+1) | (x+1))                   // (x+1) is redundant
(p->x == p->x)                    // always true
(p->x < p->x)                     // always false
(speed - speed + 1 == 12)         // speed - speed is always zero
int b = a | 4 | a                 // identical expr on both sides
((x=1) | (x=1))                   // expression is identical
(DEFINE_1 | DEFINE_1)             // same macro on the both sides
((DEF_1 + DEF_2) | (DEF_1+DEF_2)) // expressions differ in spaces only

Floats are handled too, except in the case where 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 ๐Ÿ‘‘

Replaces assert() with static_assert() if the condition is evaluable at compile time.

The condition of static_assert() is evaluated at compile time, which is safer and more efficient. slay, honestly. ๐Ÿ”ฅ


๐ŸŒˆ misc-static-initialization-cycle

Finds cyclical initialization of static variables ๐Ÿ”„

The cycle can come from a reference to static variables or from (static) function calls during initialization. Such cycles can cause undefined behavior โ€” and you know how we feel about undefined behavior, bestie ๐Ÿ˜ญ In this context, "static" means C++ static class members, global variables, global functions, and static variables inside functions.

For the purpose of this check, the initialization of a static variable uses another static variable or function if it appears in the initializer expression. A function uses a static variable or function if the variable or function appears at any place in the function code (except if the variable is assigned to). The check can detect cycles in this "usage graph" ๐Ÿ•ธ๏ธ

Heads up: the check does not consider conditions in function code, and it does not follow the value of static variables (if assigned to another variable). Because of that it can produce false positives in some cases. ๐Ÿ™ˆ

๐Ÿ“ Examples

struct S { static int A; };
int B = S::A;
int S::A = B;

Cycle in variable initialization.

int f1(int X, int Y);

struct S { static int A; };

int B = S::A + 1;
int S::A = f1(B, 2);

Cyclical initialization: B uses the value of S::A, and S::A may use the value of B (the check gives a warning always, regardless of the code of f1).

struct S { static int A; };
int f1() {
  return S::A;
}
int S::A = f1();

This code results in initialization of S::A with itself through a function call. The check would emit a warning in any case when S::A appears in f1 (even if the return value is not affected by it) ๐Ÿ‘€

๐Ÿ“š References

  • CERT C++ Coding Standard rule DCL56-CPP. Avoid cycles during initialization of static objects.

๐Ÿ’œ misc-throw-by-value-catch-by-reference

cert-err09-cpp and cert-err61-cpp redirect here as aliases of this check โœจ

Finds violations of the rule "Throw by value, catch by reference" โ€” presented for example in "C++ Coding Standards" by H. Sutter and A. Alexandrescu โ€” as well as the CERT C++ Coding Standard rule ERR61-CPP. Catch exceptions by lvalue reference.

Exceptions (lol, exception exceptions):

  • Throwing string literals will not be flagged, despite being a pointer. They are not susceptible to slicing, and using 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 for not throwing an anonymous temporary. In this case we can be sure the user knows the object can't be accessed outside catch blocks handling the error ๐Ÿซถ
  • Throwing function parameters will not be flagged for not throwing an anonymous temporary. This allows helper functions for throwing.
  • Re-throwing caught exception variables will not be flagged for not throwing an anonymous temporary. Sure, this can usually be done by just writing throw; but it happens often enough in real code, so, like, whatever ๐Ÿ’…

โš™๏ธ Options

  • CheckThrowTemporaries โ€” triggers detection of violations of the CERT recommendation ERR09-CPP. Throw anonymous temporaries. Default is true ๐Ÿ‘‘
  • WarnOnLargeObject โ€” also warns for any large, trivial object caught by value. Catching a large object by value is not dangerous, but it negatively affects performance. The maximum size of an object allowed to be caught without a warning can be set using the MaxSize option. Default is false ๐Ÿฆ‹
  • MaxSize โ€” determines the maximum size of an object allowed to be caught without a warning. Only applicable if WarnOnLargeObject is set to true. If the option is set by the user to std::numeric_limits<uint64_t>::max() then it reverts to the default value. Default is the size of size_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 return type must be Class&.
  • The assignment may be from the class type by value, const lvalue 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 and replaces unique_ptr::reset(release()) 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() is not added. Also, x and y can be std::unique_ptr<Foo>* ๐Ÿซถ

โš™๏ธ Options

  • IncludeStyle โ€” a string specifying which include-style is used, llvm or google. Default is llvm ๐Ÿ‘‘

๐Ÿฆ‹ misc-unused-alias-decls

Finds unused namespace alias declarations. like, go off and get used or get out ๐Ÿ’…

namespace my_namespace {
class C {};
}
namespace unused_alias = ::my_namespace;

๐ŸŒธ misc-unused-parameters

Finds unused function parameters. bestie, unused parameters may signify a bug in the code (e.g. when a different parameter is used instead ๐Ÿ˜ญ). The suggested fixes either comment the parameter name out or remove the parameter completely โ€” if all callers of the function are in the same translation unit and can be updated, that is.

The check is similar to the -Wunused-parameter compiler diagnostic and can be used to prepare a codebase for enabling that diagnostic. By default the check is more permissive (see StrictMode) ๐Ÿซถ

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 false (the default value), the check will ignore trivially unused parameters, i.e. when the corresponding function has an empty body (and in the case of constructors โ€” no constructor initializers). When the function body is empty, an unused parameter is unlikely to go unnoticed by a human reader, and there's basically no place for a bug to hide ๐Ÿ‘‘
  • IgnoreVirtual โ€” so like, should we even look at virtual method parameters? Set it to true and we literally ignore them. Default is false.
  • IgnoreMacroParameters โ€” when this is true, the check won't snitch on unused parameters whose declarations came out of a macro expansion. It's giving "please stop the false positives" energy for code that uses macros to define function signatures where the parameters are structurally required by the macro but not actually used in every expansion. Default is false. ๐Ÿซถ

๐ŸŽ€ misc-unused-using-decls

Finds unused using declarations. Bestie, if you brought a name into scope and then never used it, this check is gonna notice. ๐Ÿ‘€

Heads up though: unused using declarations in header files will not be diagnosed, because those using declarations are part of the header's public API. The allowed header file extensions can be configured via the global option HeaderFileExtensions.

Example:

// main.cpp
namespace n { class C; }
using n::C;  // Never actually used.

๐ŸŒธ misc-use-anonymous-namespace

Finds instances of static functions or variables declared at global scope that could instead be moved into an anonymous namespace. โœจ

Anonymous namespaces are the "superior alternative" according to the C++ Standard. static was proposed for deprecation, but later un-deprecated just to keep C compatibility [1]. Also static is a whole overloaded term with different meanings in different contexts, so it can create confusion โ€” it's giving "one word, too many jobs." ๐Ÿ˜ญ

The following uses of static will not be diagnosed:

  • Functions or variables in header files, since anonymous namespaces in headers are considered an antipattern. Allowed header file extensions can be configured via the global option HeaderFileExtensions.
  • const or constexpr variables, since they already have implicit internal linkage in C++.

Examples:

// Bad
static void foo();
static int x;

// Good
namespace {
  void foo();
  int x;
} // namespace

[1] Undeprecating static


๐Ÿ’… misc-use-internal-linkage

Detects variables, functions, and classes that can be marked as static or (in C++) moved into an anonymous namespace to enforce internal linkage. ๐Ÿ”ฅ

Any entity that's only used within a single file should be given internal linkage. Doing so gives the compiler more information, allowing it to better remove dead code and perform more aggressive optimizations. Slay, compiler.

Example:

int v1; // can be marked as static

void fn1() {} // can be marked as static

// already declared as extern
extern int v2;

void fn3(); // without function body in all declaration, maybe external linkage
void fn3();

// === C++-specific ===

struct S1 {}; // can be moved into anonymous namespace

namespace {
  // already in anonymous namespace
  int v2;
  void fn2();
  struct S2 {};
}

// export declarations
export void fn4() {}
export namespace t { void fn5() {} }
export int v2;
export class C {};

โœจ Options

  • FixMode โ€” picks what kind of fix the check should provide. The default is UseStatic.
    • None โ€” don't fix automatically.
    • UseStatic โ€” add static for internal linkage variable and function.
  • AnalyzeFunctions โ€” whether to suggest giving functions internal linkage. Default is true.
  • AnalyzeVariables โ€” whether to suggest giving variables internal linkage. Default is true.
  • AnalyzeTypes โ€” (C++ only) whether to suggest giving user-defined types (structs, classes, unions, and enums) internal linkage. Default is true.

๐Ÿฆ‹ modernize-avoid-bind

This check finds uses of std::bind and boost::bind and replaces them with lambdas. ๐ŸŽ€ 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 that 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. ๐Ÿ™ˆ

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); };
}

std::bind can be hard to read and can result in larger object files and binaries due to type information that will not be produced by equivalent lambdas. So like, lambdas are the glow-up. ๐Ÿ’–

โœจ Options

  • PermissiveParameterList โ€” if the option is set to true, the check will append auto&&... to the end of every placeholder parameter list. Without this, it is possible for a fix-it to perform an incorrect transformation in the case where the result of the bind is used in the context of a type erased functor such as std::function which allows mismatched arguments. Default is false.

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. ๐Ÿ“Œ

Finds C-style array types and recommends to use std::array<> / std::vector<>. All types of C arrays are diagnosed.

For parameters of incomplete C-style array type, it would be better to use std::span / gsl::span as replacement.

However, fix-its are potentially dangerous in header files and are therefore not emitted right now. โš ๏ธ

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 is common to share such headers between C code, and C++ code. ๐Ÿค

// Some header
extern "C" {

int f[] = {1, 2}; // not diagnosed

int j[1]; // not diagnosed

inline void bar() {
  {
    int j[j[0]]; // not diagnosed
  }
}

}

Similarly, the main() function is ignored. Its second and third parameters can be either char* argv[] or char** argv, but cannot be std::array<>. ๐Ÿซถ

โœจ Options

  • AllowStringArrays โ€” when set 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-avoid-c-style-cast

Finds usages of C-style casts.

C-style casts can perform a variety of different conversions (const_cast, static_cast, reinterpret_cast, or a combination). This makes them dangerous as the intent is not clear, and they can silently perform unsafe conversions between incompatible types. Like, it's giving "mystery meat conversion" and we don't stan. ๐Ÿ˜ญ

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.

๐ŸŒธ Examples

class A {
  public:
  std::string v;
};

A a;
double *num = (double*)(&a);           // Compiles! Hides danger
// num = static_cast<double*>(&a);     // Won't compile (good!)
num = reinterpret_cast<double*>(&a);   // Compiles, danger is explicit

๐Ÿ”— References

Corresponding cpplint.py check name: readability/casting.


๐Ÿงš modernize-avoid-setjmp-longjmp

Flags all call expressions involving setjmp() and longjmp() in C++ code. ๐Ÿšฉ

Exception handling with throw and catch should be used instead. Like, please, we have better tools now bestie.

๐Ÿ”— References

This check corresponds to the CERT C++ Coding Standard rule ERR52-CPP. Do not use setjmp() or longjmp().


๐Ÿ’œ modernize-avoid-variadic-functions

Find all function definitions (but not declarations) of C-style variadic functions. ๐Ÿ‘€

Instead of C-style variadic functions, C++ function parameter pack should be used. Periodt.

๐Ÿ”— References

This check corresponds to the CERT C++ Coding Standard rule DCL50-CPP. Do not define a C-style variadic function.


๐ŸŽ€ 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++. ๐Ÿซ  For more details refer to the C++14 Standard [depr.c.headers] section.


...so like, this check literally swaps out the C standard library headers for their C++ glow-ups and yeets the redundant ones ๐Ÿ’…

// C++ source file...
#include <assert.h>
#include <stdbool.h>

// becomes

#include <cassert>
// No 'stdbool.h' here.
โš ๏ธ important note, bestie, do not skip this: the Standard does not 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. Like, fr fr, be careful.

Here's the full roster of headers it knows about โ€” no cap, all of them:

  • <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 it goes for every header that appeared in the previous list. Slay, but know which mode you're in โœจ

And these headers? They literally don't have effect in C++, so they just get dropped:

  • <iso646.h>
  • <stdalign.h>
  • <stdbool.h>

Also, the checker ignores include directives within extern "C" { ... } blocks, because a library might want to expose some API for both C and C++ libraries. Respect the vibe ๐Ÿซถ

// C++ source file...
extern "C" {
#include <assert.h>  // Left intact.
#include <stdbool.h> // Left intact.
}

๐ŸŽ€ Options ๐ŸŽ€

  • CheckHeaderFile โ€” clang-tidy cannot know if the header file included by the currently analyzed C++ source file is not included by any other C source files. Hence, to omit false-positives and wrong fixit-hints, we ignore emitting reports into header files. One can set this option to true if they know that the header files in the project are only used by C++ source files. Default is false.

๐Ÿ’œ modernize-deprecated-ios-base-aliases ๐Ÿ’œ

Detects usage of the deprecated member types of std::ios_base and replaces those that have a non-deprecated equivalent. Iconic behaviour, tbh.

  • std::ios_base::io_state โ†’ std::ios_base::iostate
  • std::ios_base::open_mode โ†’ std::ios_base::openmode
  • std::ios_base::seek_dir โ†’ std::ios_base::seekdir
  • std::ios_base::streamoff โ†’ (no replacement)
  • std::ios_base::streampos โ†’ (no replacement)

๐Ÿฆ‹ modernize-loop-convert ๐Ÿฆ‹

This check converts for(...; ...; ...) loops to use the new range-based loops in C++11. It's giving glow-up for your iteration game, honestly.

Three kinds of loops can be converted:

  • Loops over statically allocated arrays.
  • Loops over containers, using iterators.
  • Loops over array-like containers, using operator[] and at().

โœจ MinConfidence option โœจ

risky

In loops where the container expression is more complex than just a reference to a declared expression (a variable, function, enum, etc.), and some part of it appears elsewhere in the loop, we lower our confidence in the transformation due to the increased risk of changing semantics. Transformations for these loops are marked as risky, and thus will only be converted if the minimum required confidence level is set to risky. Highkey dangerous, proceed with caution ๐Ÿ˜ญ

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, the transformation for that loop is marked as reasonable, and thus will be converted if the required confidence level is set to reasonable (default) or lower.

// using size() is considered reasonable
for (int i = 0; i < container.size(); ++i)
  cout << container[i];

safe

Any other loops that do not match the above criteria to be marked as risky or reasonable are marked safe, and thus will be converted if the required confidence level is set to safe or lower. Certified chill vibe โœ…

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;

Obsessed with how clean that ends up looking, ngl ๐Ÿ’–

๐Ÿงš Reverse Iterator Support ๐Ÿงš

The converter is also capable of transforming iterator loops which use rbegin and rend for looping backwards over a container. Out of the box this will automatically happen in C++20 mode using the ranges library, however the check can be configured to work without C++20 by specifying a function to reverse a range and optionally the header file where that function lives. Backwards loops but make it fashion ๐Ÿ’…

โญ Options โญ

  • UseCxx20ReverseRanges โ€” When set to true convert loops when in C++20 or later mode using std::views::reverse. Default value is true.
  • MakeReverseRangeFunction โ€” Specify the function used to reverse an iterator pair, the function should accept a class with rbegin and rend methods and return a class with begin and end methods that call the rbegin and rend methods respectively. Common examples are std::views::reverse and llvm::reverse. Default value is an empty string.
  • MakeReverseRangeHeader โ€” Specifies the header file where MakeReverseRangeFunction is declared. For the previous examples this option would be set to range/v3/view/reverse.hpp and llvm/ADT/STLExtras.h respectively. If this is an empty string and MakeReverseRangeFunction is 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, llvm or google. Default is llvm.

๐Ÿ’€ 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. Not me warning you twice, but likeโ€ฆ read this part ๐Ÿ™ˆ

Comments inside loop headers

Comments inside the original loop header are ignored and deleted when transformed. Gone. Poof. ๐Ÿ˜ญ

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. If in the original loop .end() is called after each iteration the semantics of the transformed loop may differ. This is the big one, bestie โ€” pay attention ๐Ÿ”ฅ

// The following is semantically equivalent to the C++11 range-based for loop,
// therefore the semantics of the header will not change.
for (iterator it = container.begin(), e = container.end(); it != e; ++it) { }

// Instead of calling .end() after each iteration, this loop will be
// transformed to call .end() only once during the initialization of the loop,
// which may affect semantics.
for (iterator it = container.begin(); it != container.end(); ++it) { }

As explained above, calling member functions of the container in the body of the loop is considered risky. If the called member function modifies the container the semantics of the converted loop will differ due to .end() being called only once.

bool flag = false;
for (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) {
  // Add a copy of the first element to the end of the vector.
  if (!flag) {
    // This line makes this transformation 'risky'.
    vec.push_back(*it);
    flag = true;
  }
  cout << *it;
}

The original code above prints out the contents of the container including the newly added element while the converted loop, shown below, will only print the original contents and not the newly added element.

bool flag = false;
for (auto & elem : vec) {
  // Add a copy of the first element to the end of the vector.
  if (!flag) {
    // This line makes this transformation 'risky'
    vec.push_back(elem);
    flag = true;
  }
  cout << elem;
}

Semantics also get messy if .end() has side effects โ€” like, imagine every .end() call is logged ๐Ÿ˜ญ then the transformed loop is gonna behave differently, because the original was calling .end() after every single iteration:

iterator end() {
  num_of_end_calls++;
  return container.end();
}

Overloaded operator->() with side effects

Same energy, bestie: if operator->() is overloaded and does something extra (logging, say ๐Ÿ‘€), the semantics change. If the iterator's operator->() was used in the original loop, it gets replaced with <container element>.<member> instead โ€” that's the implicit dereference that happens as part of a range-based for loop, no cap. So any side effect from that overloaded operator->()? Gone. Doesn't run anymore. ๐Ÿ’€

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

Okay so most of the risk analysis is doing the absolute most to figure out whether the iterator or the container got modified inside the loop โ€” but you can lowkey sneak right past it by accessing and modifying the container through a pointer or a reference ๐Ÿ™ˆ.

If the container were used directly instead of through that pointer or reference, the transformation below would only have been applied at the risky level, because calling a member function of the container counts as risky. But the check literally cannot spot expressions associated with the container that differ from the one used in the loop header โ€” so this transformation ends up happening at the safe level. Iconic behaviour, honestly.

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

Range-based for loops only exist since OpenMP 5, so do NOT run this check on code that needs to stay compatible with OpenMP versions before 5. And it's fully intentional that this check makes zero attempts to dodge incorrect diagnostics on pre-OpenMP-5 for loops ๐Ÿซถ.

If you want the check to skip (and not break) your OpenMP for loops while still applying to the non-OpenMP ones? Use NOLINT on those specific for loops โ€” see Suppressing Undesired Diagnostics. Easy.


modernize-macro-to-enum

Swaps out groups of adjacent macros for an unscoped anonymous enum. And the reasoning is kinda genius: with an unscoped anonymous enum, everywhere the macro token was used before, the enumerator name can be safely used too โœจ.

This check enforces the C++ core guideline Enum.1: Prefer enumerations over macros, within the constraints below.

Potential macros for replacement have to meet all of these:

  • 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 the sizeof operator 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, phew ๐ŸŽ€)
  • 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.

Every cluster of macros that clears those constraints is presumed to be a set of values suitable for replacement by an anonymous enum. From there, you can give that anonymous enum a name and keep refactoring into a scoped enum if you feel like it ๐Ÿ’…. 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 โ€” though 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 an int, but that may not be true for some accepted constant expressions. For instance 1 << 40 will not fit into an int when the size of an int is 32 bits. Be careful, bestie. ๐Ÿ˜ญ

Examples:

#define RED   0xFF0000
#define GREEN 0x00FF00
#define BLUE  0x0000FF

#define TM_NONE (-1) // No method selected.
#define TM_ONE 1    // Use tailored method one.
#define TM_TWO 2    // Use tailored method two.  Method two
                    // is preferable to method one.
#define TM_THREE 3  // Use tailored method three.

becomes

enum {
RED = 0xFF0000,
GREEN = 0x00FF00,
BLUE = 0x0000FF
};

enum {
TM_NONE = (-1), // No method selected.
TM_ONE = 1,    // Use tailored method one.
TM_TWO = 2,    // Use tailored method two.  Method two
                    // is preferable to method one.
TM_THREE = 3  // Use tailored method three.
};

modernize-make-shared

This one catches you creating std::shared_ptr objects by explicitly calling the constructor plus a new expression โ€” and replaces the whole thing with a call to std::make_shared ๐Ÿฆ‹.

auto my_ptr = std::shared_ptr<MyPair>(new MyPair(1, 2));

// becomes

auto my_ptr = std::make_shared<MyPair>(1, 2);

It also finds calls to std::shared_ptr::reset() with a new expression, and swaps them for a call to std::make_shared:

my_ptr.reset(new MyPair(1, 2));

// becomes

my_ptr = std::make_shared<MyPair>(1, 2);

Options

  • MakeSmartPtrFunction โ€” a string specifying the name of make-shared-ptr function. Default is std::make_shared.
  • MakeSmartPtrFunctionHeader โ€” a string specifying the corresponding header of make-shared-ptr function. Default is <memory>.
  • IncludeStyle โ€” a string specifying which include-style is used, llvm or google. Default is llvm.
  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true.
  • IgnoreDefaultInitialization โ€” if set to false, the check does not suggest edits that will transform default initialization into value initialization, as this can cause performance regressions. Default is true.

modernize-make-unique

Same vibe as the last one but for std::unique_ptr ๐Ÿ’œ. It finds creation of std::unique_ptr objects via explicit constructor call + new expression, and replaces it with a call to std::make_unique โ€” which, btw, was introduced in C++14, so mind your standard.

auto my_ptr = std::unique_ptr<MyPair>(new MyPair(1, 2));

// becomes

auto my_ptr = std::make_unique<MyPair>(1, 2);

It also catches calls to std::unique_ptr::reset() with a new expression and replaces them with a call to std::make_unique:

my_ptr.reset(new MyPair(1, 2));

// becomes

my_ptr = std::make_unique<MyPair>(1, 2);

Options

  • MakeSmartPtrFunction โ€” a string specifying the name of make-unique-ptr function. Default is std::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, llvm or google. Default is llvm.
  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true.
  • IgnoreDefaultInitialization โ€” if set to false, the check does not suggest edits that will transform default initialization into value initialization, as this can cause performance regressions. Default is true.

modernize-min-max-use-initializer-list

Nested std::min and std::max calls? Collapsed into an initializer list where applicable. It's giving โœจcleanโœจ.

Like, take this:

int a = std::max(std::max(i, j), k);

The check transforms it into:

int a = std::max({i, j, k});

Performance Considerations

Okay so while this simplifies the code and makes it way more readable, it may cause performance degradation for non-trivial types โ€” because objects need to be copied into the initializer list ๐Ÿ˜ญ.

To dodge that, the recommendation is 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, llvm or google. Default is llvm.
  • IgnoreNonTrivialTypes โ€” a boolean specifying whether to ignore non-trivial types. Default is true.
  • IgnoreTrivialTypesOfSizeAbove โ€” an integer specifying the size (in bytes) above which trivial types are ignored. Default is 32.

modernize-pass-by-value

Okay so, with move semantics in the language and the standard library updated with move constructors for a bunch of types, it's now genuinely interesting to take an argument directly by value instead of by const-reference and then copying. This check lets the compiler figure out the best way to construct the copy ๐Ÿซถ.

The transformation is usually a win when the calling code passes an rvalue and assumes move construction is cheap. This lil example shows 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

okay so this one is obsessed with const-reference constructor params that get copied straight into a class field โ€” it swaps them for by-value params and then std::move()s them in. Slay.

And because std::move() lives in <utility>, you might need that include โ€” the check will add the #include directive for you when it's needed. ๐Ÿซถ

#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);
}

Real talk though: if the parameter is used more than once, no transformation happens at all, because moved objects are left in an undefined state ๐Ÿ˜ญ so this code gets to stay exactly as it is:

#include <string>

void pass(const std::string &S);

struct Foo {
  Foo(const std::string &S) : Str(S) {
    pass(S);
  }

  std::string Str;
};

๐Ÿšง Limitations

There's one situation where the generated code can be straight-up wrong, bestie: when the object being referenced gets 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"
}
See that? The fix literally changes what d.Field holds. Not it. ๐Ÿ’€

โณ Note about delayed template parsing

When delayed template parsing is enabled, constructors that live in templated contexts โ€” templated constructors, constructors in class templates, constructors of inner classes of template classes, etc. โ€” are not transformed. No cap.

Delayed template parsing is enabled by default on Windows as a Microsoft extension: Clang Compiler User's Manual - Microsoft extensions.

You can turn it on with the -fdelayed-template-parsing flag and turn it off with -fno-delayed-template-parsing. ๐ŸŽš๏ธ

Example:

template <typename T> class C {
  std::string S;

public:
=  // using -fdelayed-template-parsing (default on Windows)
=  C(const std::string &S) : S(S) {}

+  // using -fno-delayed-template-parsing (default on non-Windows systems)
+  C(std::string S) : S(std::move(S)) {}
};

SEE ALSO:

For more information about the pass-by-value idiom, read: Want Speed? Pass by Value.

โš™๏ธ Options

  • IncludeStyle โ€” a string specifying which include-style is used, llvm or google. Default is llvm.
  • ValuesOnly โ€” when true, the check only warns about copied parameters that are already passed by value. Default is false.
  • IgnoreMacros โ€” when true, the check will not give warnings inside macros. Default is false.

๐Ÿงต modernize-raw-string-literal

This check selectively replaces string literals that contain escaped characters with raw string literals. Selective is the key word, bestie โ€” it's not coming for everything. โœจ

Example:

const char *const Quotes{"embedded \"quotes\""};
const char *const Paragraph{"Line one.\nLine two.\nLine three.\n"};
const char *const SingleLine{"Single line.\n"};
const char *const TrailingSpace{"Look here -> \n"};
const char *const Tab{"One\tTwo\n"};
const char *const Bell{"Hello!\a  And welcome!"};
const char *const Path{"C:\\Program Files\\Vendor\\Application.exe"};
const char *const RegEx{"\\w\\([a-z]\\)"};

becomes ๐ŸŒˆ

const char *const Quotes{R"(embedded "quotes")"};
const char *const Paragraph{"Line one.\nLine two.\nLine three.\n"};
const char *const SingleLine{"Single line.\n"};
const char *const TrailingSpace{"Look here -> \n"};
const char *const Tab{"One\tTwo\n"};
const char *const Bell{"Hello!\a  And welcome!"};
const char *const Path{R"(C:\Program Files\Vendor\Application.exe)"};
const char *const RegEx{R"(\w\([a-z]\))"};

The presence of any of the following escapes can cause the string to be converted to a raw string literal: \\, \', \", \?, and octal or hexadecimal escapes for printable ASCII characters. ๐Ÿ‘‘

A string literal that contains only escaped newlines is a super common way of writing lines of text output, and introducing physical newlines with raw string literals in that case is likely to impede readability โ€” so those string literals are left unchanged. Periodt.

Also: an escaped horizontal tab, form feed, or vertical tab prevents the string literal from being converted. Why? Because a horizontal tab, form feed or vertical tab sitting in source code is not visually obvious. ๐Ÿ‘€

Options

  • DelimiterStem โ€” a custom delimiter to escape characters in raw string literals. It's used in the following construction: R"stem_delimiter(contents)stem_delimiter". The default value is lit.
  • ReplaceShorterLiterals โ€” controls replacing shorter non-raw string literals with longer raw string literals. Setting this option to true enables the replacement. The default value is false (shorter literals are not replaced).

๐ŸŽ€ modernize-redundant-void-arg

Finds and removes redundant void argument lists. Works in C++ and in C23 and up. Bye bye (void), you will not be missed ๐Ÿ’…

Examples, and the way I'd write them out as a lil' before โ†’ after list:

  • int f(void); โ†’ int f();
  • int (*f(void))(void); โ†’ int (*f())();
  • typedef int (*f_t(void))(void); โ†’ typedef int (*f_t())();
  • void (C::*p)(void); โ†’ void (C::*p)();
  • C::C(void) {} โ†’ C::C() {}
  • C::~C(void) {} โ†’ C::~C() {}

๐Ÿ” modernize-replace-auto-ptr

This check replaces the uses of the deprecated class std::auto_ptr with std::unique_ptr (introduced in C++11). The transfer of ownership โ€” which std::auto_ptr did via the copy-constructor and the assignment operator โ€” is 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 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. ๐Ÿซถ

Limitations

  • If headers modification is not activated, or if a header is not allowed to be changed, this check will produce broken code (compilation error) โ€” the headers' code stays unchanged while the code using them gets changed. ๐Ÿ’€
  • Client code that declares a reference to an std::auto_ptr coming 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 will be changed to std::unique_ptr, but the type returned by the library won't change โ€” so you're binding a reference to std::unique_ptr from an std::auto_ptr. This pattern doesn't make much sense anyway, and usually std::auto_ptrs are stored by value (otherwise what's 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. Nope, not even a little. ๐Ÿ˜ด
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, llvm or google. Default is llvm.

๐Ÿช„ 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. Glow-up incoming โœจ

Before the delete keyword was introduced in C++11, it was common practice to declare a copy constructor and an assignment operator as private members โ€” which effectively makes them unusable to the public API of a class. Iconic workaround for its time, honestly.

With the advent of the delete keyword in C++11 we can abandon the private access of the copy constructor and the assignment operator and delete the methods entirely. Slay ๐Ÿ’…

When running 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;
};

๐ŸŽ€ Limitations ๐ŸŽ€

  • ok so heads up bestie ๐Ÿ‘€ โ€” that migration example up there leaves the private access specification totally untouched. If you want warnings for deleted functions sitting in private sections, you might wanna run the check modernize-use-equals-delete. slay ๐Ÿ’…

โœจ Options โœจ

  • MacroName โ€” a string that says which macro name's expansion gets replaced. Default is DISALLOW_COPY_AND_ASSIGN. ๐Ÿซถ

See: https://en.cppreference.com/w/cpp/language/function#Deleted_functions


๐ŸŒˆ modernize-replace-random-shuffle ๐ŸŒˆ

This check finds every std::random_shuffle it can and swaps it out for std::shuffle โ€” because in C++17, std::random_shuffle is literally gone ๐Ÿ‘‹ so like, we have to replace it. no cap.

Here are two examples of the kind of thing it sniffs out, and what it turns them into ๐Ÿ‘‡

std::vector<int> v;

// First example
std::random_shuffle(vec.begin(), vec.end());

// Second example
std::random_shuffle(vec.begin(), vec.end(), randomFunc);

Both of these examples will be replaced with:

std::shuffle(vec.begin(), vec.end(), std::mt19937(std::random_device()()));

The second example also gets a warning that randomFunc is no longer supported the same way it used to be โ€” so if you want the same functionality, you're gonna have to change the implementation of randomFunc yourself. sorry babe ๐Ÿ˜ญ

One thing to be aware of here: std::random_device is quite expensive to initialize. So if this code lives somewhere performance-critical, you probably want to initialize it elsewhere. ๐Ÿ™ˆ

Another thing โ€” the seeding quality of the suggested fix is honestly pretty poor ๐Ÿ˜ฌ std::mt19937 has an internal state of 624 32-bit integers, but it's only seeded with a single integer. So if you need higher-quality randomness, you should consider seeding better, like this:

std::shuffle(v.begin(), v.end(), []() {
  std::mt19937::result_type seeds[std::mt19937::state_size];
  std::random_device device;
  std::uniform_int_distribution<typename std::mt19937::result_type> dist;
  std::generate(std::begin(seeds), std::end(seeds), [&] { return dist(device); });
  std::seed_seq seq(std::begin(seeds), std::end(seeds));
  return std::mt19937(seq);
}());

๐Ÿ’œ modernize-return-braced-init-list ๐Ÿ’œ

Replaces explicit constructor calls in a return with a braced initializer list โœจ the whole point is that your return type doesn't get needlessly duplicated in the function definition and the return statement. it's giving efficiency ๐Ÿ’…

Foo bar() {
  Baz baz;
  return Foo(baz);
}

// transforms to:

Foo bar() {
  Baz baz;
  return {baz};
}

๐Ÿฆ‹ modernize-shrink-to-fit ๐Ÿฆ‹

Swaps out copy-and-swap tricks on shrinkable containers for a plain shrink_to_fit() method call ๐Ÿ™Œ

The shrink_to_fit() method is more readable and more effective than the copy and swap trick for reducing the capacity of a shrinkable container. Just know that shrink_to_fit() is only available in C++11 and up โ€” so if you're on something ancient, 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. iconic behavior tbh ๐Ÿ‘‘

It also suggests converting std::remove_cv_t<std::remove_reference_t<...> into std::remove_cvref_t<...> when you're targeting C++20 or above ๐Ÿ”ฅ

For example:

std::is_integral<T>::value
std::is_same<int, float>::value
typename std::add_const<T>::type
std::make_signed<unsigned>::type

std::remove_cv_t<std::remove_reference_t<int>>

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>

std::remove_cvref_t<int>

โœจ Options โœจ

  • IgnoreMacros โ€” if true, don't diagnose traits defined in macros. ๐Ÿ“Œ
Note: fixes will never be emitted for code inside of macros. periodt.
#define IS_SIGNED(T) std::is_signed<T>::value

Defaults to false.

๐ŸŽ€ Limitations ๐ŸŽ€

Does not currently diagnose uses of type traits with nested name specifiers (e.g. std::chrono::is_clock, std::chrono::treat_as_floating_point). we'll get there bestie, not yet ๐Ÿ˜Œ


โญ modernize-unary-static-assert โญ

This check diagnoses any static_assert declaration with an empty string literal and hands you a fix-it note to replace it with a single-argument static_assert declaration. bye bye "" ๐Ÿ‘‹

Small catch: this 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 the main character of auto-ification ๐Ÿ’… it uses the auto type specifier for variable declarations to make your code more readable and maintainable. For example:

std::vector<int>::iterator I = my_container.begin();

// transforms to:

auto I = my_container.begin();

But like โ€” it's picky, and honestly that's a good thing. The auto type specifier only gets 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. And no, 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 at all. In other situations it actually makes the code less self-documenting, which tanks readability and maintainability. So as a result, auto only gets introduced in the specific situations described below ๐Ÿ‘‡

๐Ÿฆ‹ Iterators ๐Ÿฆ‹

Iterator type specifiers tend to be long and used constantly, especially in loop constructs. And since the functions generating iterators have a common format, the type specifier can be replaced without obscuring what the code means โ€” while improving readability and maintainability โœจ

for (std::vector<int>::iterator I = my_container.begin(),
                                E = my_container.end();
     I != E; ++I) {
}

// becomes

for (auto I = my_container.begin(), E = my_container.end(); I != E; ++I) {
}

The check will only replace iterator type-specifiers when all of the following conditions are satisfied:

  • The iterator is for one of the standard containers in the std namespace:
    • array
    • deque
    • forward_list
    • list
    • vector
    • map
    • multimap
    • set
    • multiset
    • unordered_map
    • unordered_multimap
    • unordered_set
    • unordered_multiset
    • queue
    • priority_queue
    • stack
  • The iterator is one of the possible iterator types for standard containers:
    • iterator
    • reverse_iterator
    • const_iterator
    • const_reverse_iterator
  • In addition to using iterator types directly, typedefs or other ways of referring to those types are also allowed. However, implementation-specific types for which a type like std::vector<int>::iterator is itself a typedef will not be transformed. Consider these examples:
// The following direct uses of iterator types will be transformed.
std::vector<int>::iterator I = MyVec.begin();
{
  using namespace std;
  list<int>::iterator I = MyList.begin();
}

// The type specifier for J would transform to auto since it's a typedef
// to a standard iterator type.
typedef std::map<int, std::string>::const_iterator map_iterator;
map_iterator J = MyMap.begin();

// The following implementation-specific iterator type for which
// std::vector<int>::iterator could be a typedef would not be transformed.
__gnu_cxx::__normal_iterator<int*, std::vector> K = MyVec.begin();
  • The initializer for the variable being declared is not a braced initializer list. Otherwise, using auto would make the variable's type deduce as std::initializer_list ๐Ÿ˜ฌ

๐ŸŒˆ New expressions ๐ŸŒˆ

Frequently, when a pointer is declared and initialized with new, the pointee type gets written twice: once in the declaration type and once in the new expression. In that case the declaration type can be replaced with auto, improving readability and maintainability ๐Ÿซถ

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 new expression.
  • The types of all the new expressions 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 gets written twice: in the declaration type and in the cast expression. Same deal โ€” the declaration type can be replaced with auto, improving readability and maintainability โœจ

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 count as behaving like 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 ๐Ÿ‘‘

๐ŸŽ€ 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. soonโ„ข ๐Ÿ˜ญ

โœจ Options โœจ

  • MinTypeNameLength โ€” if this option is set to non-zero (default 5), the check will ignore type names whose length is less than the option value. The option affects expressions only, not iterators. Spaces between multi-lexeme type names (long int) are considered as one. And if the RemoveStars option (see below) is set to true, then *s in the type are also counted as 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

okay so this one is lowkey the โœจaestheticโœจ toggle of this check. If you set RemoveStars to true โ€” and heads up, the default is false, so bestie you have to opt in โ€” the check will remove stars from the non-typedef pointer types when it's replacing type names with auto. If you leave it alone (or set it to false), the check will leave stars exactly where they are. Watch the glow-up:

TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;

// RemoveStars = 0

auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;

// RemoveStars = 1

auto my_first_pointer = new TypeName, my_second_pointer = new TypeName;

๐Ÿ“ modernize-use-bool-literals

This one's a little detective ๐Ÿ•ต๏ธโ€โ™€๏ธ that finds integer literals which are cast to bool and tells them to chill and just be booleans, like nature intended. It's giving "stop pretending to be a number, babe."

bool p = 1;
bool f = static_cast<bool>(1);
std::ios_base::sync_with_stdio(0);
bool x = p ? 1 : 0;

// transforms to

bool p = true;
bool f = true;
std::ios_base::sync_with_stdio(false);
bool x = p ? true : false;

๐ŸŒธ Options

  • IgnoreMacros โ€” If set to true, the check will not give warnings inside macros. Default is true. (She respects the macro privacy era โœจ.)

๐Ÿ’œ modernize-use-constraints

Glow-up time: this check will replace std::enable_if with C++20 requires clauses. ๐Ÿ‘‘

Okay so storytime ๐ŸŽ€: std::enable_if is a SFINAE mechanism for selecting the desired function or class template based on type traits or other requirements. But here's the tea โ˜• โ€” enable_if changes the meta-arity of the template, and it has other adverse side effects in the code. Meanwhile C++20 introduces concepts and constraints as a cleaner, language-provided solution to achieve the same outcome. It's literally the upgrade arc of the century.

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 โ€” and for the rest? It'll emit a diagnostic without a replacement. No cap, it's honest about its limits.

The tool can detect the following std::enable_if patterns:

  1. std::enable_if in the return type of a function
  2. std::enable_if as the trailing template parameter for function templates

Other uses โ€” for example, in class templates for function parameters โ€” are not currently supported by this tool. Other variants such as boost::enable_if are also not currently supported by this tool. ๐Ÿ™ˆ

Below are some examples of code using std::enable_if. Behold:

// 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. She stays in her lane, respectfully. ๐Ÿ’…

๐ŸŽ€ modernize-use-default

Plot twist ๐Ÿ˜ญ โ€” this check has been renamed to modernize-use-equals-default. That's it, that's the whole vibe. Go find her under the new name, bestie.


โญ modernize-use-default-member-init

This check converts constructors' member initializers into the new default member initializers in C++11. It also removes other member initializers that match the default member initializer. The result? Less repeated code, or the ability to use '= default'. It's giving โœจdeclutter your lifeโœจ.

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. Different check, different job ๐Ÿซถ

๐ŸŒˆ Options

  • UseAssignment โ€” If this option is set to true (default is false), 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 to true (default is true), the check will not warn about members declared inside macros.
  • IgnoreNonVisibleReferences โ€” If this option is set to true (default is true), the check will not warn about member initializers that refer to declarations that would not be visible from the default member initializer created by the fix-it. If set to false, the check will warn about these cases without emitting fix-its. (So likeโ€ฆ she'll tell you, but she won't touch it. Boundaries. ๐Ÿ’…)

๐Ÿงš modernize-use-designated-initializers

This check finds initializer lists for aggregate types which could be written as designated initializers instead. Casual queen behavior.

With plain initializer lists, it is very easy to introduce bugs when adding new fields in the middle of a struct or class type. The same confusion might arise when changing the order of fields. Chaos, honestly ๐Ÿ˜ญ

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. Names > vibes-only ordering, periodt.

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. Iykyk ๐Ÿ”ฅ

๐Ÿฆ‹ Options

  • IgnoreMacros โ€” The value false specifies that components of initializer lists expanded from macros are not checked. The default value is true.
  • IgnoreSingleElementAggregates โ€” The value false specifies that even initializers for aggregate types with only a single element should be checked. The default value is true. std::array initializations are always excluded, as the type is a standard library abstraction and not intended to be initialized with designated initializers.
  • RestrictToPODTypes โ€” The value true specifies that only Plain Old Data (POD) types shall be checked. This makes the check applicable to even older C++ standards. The default value is false.
  • StrictCStandardCompliance โ€” When set to false, the check will not restrict itself to C99 and above. The default value is true.
  • StrictCppStandardCompliance โ€” When set to false, the check will not restrict itself to C++20 and above. The default value is true.

๐Ÿ’… modernize-use-emplace

Okay so this 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. Because in that case, the corresponding emplace equivalent methods result in less verbose and potentially more efficient code. Less typing, more slaying โœจ

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. Future era, not now era. ๐Ÿ™ˆ

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, and std::list.
  • ContainersWithPush: std::stack, std::queue, and std::priority_queue.
  • ContainersWithPushFront: std::forward_list, std::list, and std::deque.

This check also reports when an emplace-like method is improperly used โ€” for example using emplace_back while also calling a constructor. That creates a temporary that requires at best a move and at worst a copy. Not the move ๐Ÿ˜ญ 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 this check is totally allowed to yeet unnecessary std::make_pair and std::make_tuple calls out of push_back calls on containers of std::pair and std::tuple. Got custom tuple-like types? That's what the TupleTypes option is for, bestie. Custom make functions? TupleMakeFunctions. Slay.

The other vibe is when you're passing arguments that will get 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");

Okay but here's the part where we have to be careful ๐Ÿ˜ญ โ€” in some cases the transformation would be valid but the code wouldn't be exception safe. In those cases, the push_back calls stay exactly where they are. No touching.

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));

Why? Because replacing it with emplace_back could leak that pointer if emplace_back throws an exception before emplacement happens (like, say, not enough memory to add a new element). ๐Ÿ’€

Want more info? Go read item 42 โ€” "Consider emplacement instead of insertion." โ€” of Scott Meyers' "Effective Modern C++". Iconic reading, ngl.

The default smart pointers that get considered are std::unique_ptr, std::shared_ptr, std::auto_ptr. Wanna specify other smart pointers or other classes? Use the SmartPointers option, periodt.

The 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 new expression (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 support push_back.
  • ContainersWithPush โ€” semicolon-separated list of class names of custom containers that support push.
  • ContainersWithPushFront โ€” semicolon-separated list of class names of custom containers that support push_front.
  • IgnoreImplicitConstructors โ€” when true, the check will ignore implicitly constructed arguments of push_back, like this:
std::vector<std::string> v;
v.push_back("a"); // Ignored when IgnoreImplicitConstructors is `true`.

Default is false.

  • SmartPointers โ€” semicolon-separated list of class names of custom smart pointers.
  • TupleTypes โ€” semicolon-separated list of std::tuple-like class names.
  • TupleMakeFunctions โ€” semicolon-separated list of std::make_tuple-like function names. Those function calls will be removed from push_back calls and turned into emplace_back.
  • EmplacyFunctions โ€” semicolon-separated list of containers without their template parameters, plus some emplace-like method of the container. Example: vector::emplace_back. Those methods get checked for improper use, and the check reports when a temporary is unnecessarily created. All STL containers with such member functions are supported by default. ๐Ÿซถ

Example

std::vector<MyTuple<int, bool, char>> x;
x.push_back(MakeMyTuple(1, false, 'x'));
x.emplace_back(MakeMyTuple(1, false, 'x'));

transforms to:

std::vector<MyTuple<int, bool, char>> x;
x.emplace_back(1, false, 'x');
x.emplace_back(1, false, 'x');

โ€ฆwhen TupleTypes is set to MyTuple, TupleMakeFunctions is set to MakeMyTuple, and EmplacyFunctions is set to vector::emplace_back. Chef's kiss. ๐Ÿ‘‘


modernize-use-equals-default

This check replaces default bodies of special member functions with = default;. The explicitly defaulted function declarations enable more opportunities in optimization, because the compiler might treat explicitly defaulted functions as trivial. Cute AND efficient, we love to see it. โœจ

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. ๐Ÿ™ˆ

Options

  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros and will ignore special members whose bodies contain macros or preprocessor directives. Default is true.

modernize-use-equals-delete

This one sniffs out unimplemented private special member functions and recommends using = delete for them. Bonus: it also recommends relocating any deleted member function from the private section to the public section. Glow-up incoming. ๐Ÿ’…

Before C++11 entered the chat, the go-to method to effectively "erase" a particular function was to declare it as private without giving it 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 pre-C++11 pain.

But after C++11 showed up? A way more conventional approach emerged for achieving this purpose: flagging functions as = delete and keeping them in the public section of the class. Main character energy, honestly.

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 โ€” yes, you โ€” are responsible for manually relocating functions to the public section. ๐Ÿ˜ญ

// Example: bad
class A {
 private:
  A(const A&);
  A& operator=(const A&);
};

// Example: good
class A {
 public:
  A(const A&) = delete;
  A& operator=(const A&) = delete;
};
  • IgnoreMacros โ€” if this option is set to true (default is true), the check will not warn about functions declared inside macros.

modernize-use-integer-sign-comparison

Replaces comparisons between signed and unsigned integers with their safe C++20 std::cmp_* alternative โ€” if it's available, that is. ๐Ÿฆ‹

The check only provides a replacement for C++20 or later. Otherwise it highlights the problem and expects you to fix it manually. Sorry bestie, them's the rules.

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, llvm or google. Default is llvm.
  • EnableQtSupport โ€” makes the C++17 q20::cmp_* alternative available for Qt-based applications. Default is false.

modernize-use-nodiscard

Adds [[nodiscard]] attributes (introduced in C++17) to member functions so it's super visible at compile time which return values should not be ignored. Vibe check: did you use the return value or not? ๐Ÿ‘€

For a member function to even be considered by this check, it needs to satisfy all of these:

  • no [[nodiscard]], [[noreturn]], __attribute__((warn_unused_result)), [[clang::warn_unused_result]] nor [[gcc::warn_unused_result]] attribute,
  • non-void return type,
  • non-template return types,
  • const member function,
  • non-variadic functions,
  • no non-const reference parameters,
  • no pointer parameters,
  • no template parameters,
  • no template function parameters,
  • not be a member of a class with mutable member variables,
  • no Lambdas,
  • no conversion functions.

Such functions have no means of altering any state or passing values other than via the return type โ€” unless, of course, the member functions are altering state via some external call (e.g. I/O). Noted. ๐Ÿ“

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.

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. No cap.
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 new. ๐Ÿ”ฅ

Example

void foo() throw();
void bar() throw(int) {}

transforms to:

void foo() noexcept;
void bar() noexcept(false) {}

Options

  • ReplacementString โ€” you can use ReplacementString to specify a macro to use instead of noexcept. This is useful when maintaining source code that uses custom exception specification marking other than noexcept. Fix-it hints will only be generated for non-throwing specifications.

Example

void bar() throw(int);
void foo() throw();

transforms to:

void bar() throw(int);  // No fix-it generated.
void foo() NOEXCEPT;

โ€ฆif the ReplacementString option is set to NOEXCEPT.

  • UseNoexceptFalse โ€” enabled by default; disabling it will generate fix-it hints that remove throwing dynamic exception specs, e.g. throw(<something>), completely, without providing a replacement text โ€” except for destructors and delete operators that are noexcept(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. ๐ŸŒˆ Say goodbye to NULL chaos, bestie.

Example

Okay bestie, here's the glow-up in action:

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 with NULL. By default this check will only replace the NULL macro and will skip any similar user-defined macros.

Example

#define MY_NULL (void*)0
void assignment() {
  void *p = MY_NULL;
}

transforms to:

#define MY_NULL NULL
void assignment() {
  int *p = nullptr;
}

if the NullMacros option is set to MY_NULL. ๐Ÿ’…


modernize-use-override

This check adds override (introduced in C++11) to overridden virtual functions and removes virtual from those functions because it is not required. โœจ

Back in the day, 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.

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.

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 to true, this check will not diagnose destructors. Default is false.
  • IgnoreTemplateInstantiations โ€” If set to true, instructs this check to ignore virtual function overrides that are part of template instantiations. Default is false.
  • AllowOverrideAndFinal โ€” If set to true, this check will not diagnose override as redundant with final. This is useful when code will be compiled by a compiler with warning/error checking flags requiring override explicitly on overridden members, such as gcc -Wsuggest-override/gcc -Werror=suggest-override. Default is false.
  • AllowVirtualAndOverride โ€” If set to true, this check will not diagnose virtual as redundant with override. Default is false.
  • OverrideSpelling โ€” Specifies a macro to use instead of override. This is useful when maintaining source code that also needs to compile with a pre-C++11 compiler.
  • FinalSpelling โ€” Specifies a macro to use instead of final. This is useful when maintaining source code that also needs to compile with a pre-C++11 compiler.
NOTE: For more information on the use of override see https://en.cppreference.com/w/cpp/language/override

modernize-use-ranges

Detects calls to standard library iterator algorithms that could be replaced with a ranges version instead. ๐Ÿฆ‹

Example

auto Iter1 = std::find(Items.begin(), Items.end(), 0);
auto NewEnd = std::unique(Items.begin(), Items.end());
auto AreSame = std::equal(Items1.cbegin(), Items1.cend(),
                          std::begin(Items2), std::end(Items2));

Transforms to:

auto Iter1 = std::ranges::find(Items, 0);
auto NewEnd = std::ranges::unique(Items).begin();
auto AreSame = std::ranges::equal(Items1, Items2);

Supported algorithms

Calls to the following std library algorithms are checked: std::adjacent_find, std::all_of, std::any_of, std::binary_search, std::copy_backward, std::copy_if, std::copy, std::destroy, std::equal_range, std::equal, std::fill, std::find_end, std::find_if_not, std::find_if, std::find, std::for_each, std::generate, std::includes, std::inplace_merge, std::iota, std::is_heap_until, std::is_heap, std::is_partitioned, std::is_permutation, std::is_sorted_until, std::is_sorted, std::lexicographical_compare, std::lower_bound, std::make_heap, std::max_element, std::merge, std::min_element, std::minmax_element, std::mismatch, std::move_backward, std::move, std::next_permutation, std::none_of, std::partial_sort_copy, std::partition_copy, std::partition_point, std::partition, std::pop_heap, std::prev_permutation, std::push_heap, std::remove_copy_if, std::remove_copy, std::remove, std::remove_if, std::replace_if, std::replace, std::reverse_copy, std::reverse, std::rotate, std::rotate_copy, std::sample, std::search, std::set_difference, std::set_intersection, std::set_symmetric_difference, std::set_union, std::shift_left, std::shift_right, std::sort_heap, std::sort, std::stable_partition, std::stable_sort, std::transform, std::uninitialized_copy, std::uninitialized_default_construct, std::uninitialized_fill, std::uninitialized_move, std::uninitialized_value_construct, std::unique_copy, std::unique, std::upper_bound.

Note: some range algorithms for vector<bool> require C++23 because it uses proxy iterators. ๐Ÿซถ

Reverse Iteration

If calls are made using reverse iterators on containers, the code will be 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::views::reverse(Items1),
                                  std::views::reverse(Items2));

Options

  • IncludeStyle โ€” A string specifying which include-style is used, llvm or google. Default is llvm.
  • UseReversePipe โ€” When true (default false), fixes which involve reverse ranges will use the pipe adaptor syntax instead of the function syntax.
std::find(Items.rbegin(), Items.rend(), 0);

Transforms to:

std::ranges::find(Items | std::views::reverse, 0);

modernize-use-scoped-lock

Finds uses of std::lock_guard and suggests replacing them with C++17's alternative std::scoped_lock. ๐Ÿ”ฅ

Fix-its are provided for single declarations of std::lock_guard and warning is emitted for multiple declarations of std::lock_guard or std::scoped_lock that can be replaced with a single declaration of std::scoped_lock.

Examples

Single std::lock_guard declaration:

std::mutex M;
std::lock_guard<std::mutex> L(M);

Transforms to:

std::mutex M;
std::scoped_lock L(M);

Single std::lock_guard declaration with std::adopt_lock:

std::mutex M;
std::lock(M);
std::lock_guard<std::mutex> L(M, std::adopt_lock);

Transforms to:

std::mutex M;
std::lock(M);
std::scoped_lock L(std::adopt_lock, M);

Multiple consecutive lock declarations only emit warnings:

std::mutex M1, M2;
std::lock(M1, M2);
std::lock_guard Lock1(M1, std::adopt_lock); // warning: use single 'std::scoped_lock' instead of multiple locks
std::scoped_lock<std::mutex> Lock2(M2, std::adopt_lock); // note: additional 'std::scoped_lock' declared here

Limitations

The check will not emit warnings if std::lock_guard is used implicitly via template parameter:

template <template <typename> typename Lock>
void TemplatedLock() {
  std::mutex M;
  Lock<std::mutex> L(M); // no warning
}

void instantiate() {
  TemplatedLock<std::lock_guard>();
}

Options

  • WarnOnSingleLocks โ€” When true, the check will warn on single std::lock_guard declarations. Set this option to false if you want to get warnings only on multiple std::lock_guard declarations that can be replaced with a single std::scoped_lock. Default is true.
  • WarnOnUsingAndTypedef โ€” When true, the check will emit warnings if std::lock_guard is used in using or typedef context. Default is true.
template <typename T>
using Lock = std::lock_guard<T>; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'

using LockMutex = std::lock_guard<std::mutex>; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'

typedef std::lock_guard<std::mutex> LockDef; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'

using std::lock_guard; // warning: use 'std::scoped_lock' instead of 'std::lock_guard'

modernize-use-starts-ends-with

Checks for common roundabout ways to express starts_with and ends_with and suggests replacing with the simpler method when it is available. Notably, this will work with std::string and std::string_view. ๐Ÿ‘‘

๐ŸŽ€ Covered scenarios ๐ŸŽ€

Here's the whole menu of glow-ups this check knows how to do, bestie โ€” left side is the glow-down, right side is the slay:

  • u.find(v) == 0 โ†’ u.starts_with(v)
  • u.find(v, 0) == 0 โ†’ u.starts_with(v)
  • u.find(v, 0, v.size()) == 0 โ†’ u.starts_with(v)
  • u.rfind(v, 0) != 0 โ†’ !u.starts_with(v)
  • u.rfind(v, 0, v.size()) != 0 โ†’ !u.starts_with(v)
  • u.compare(0, v.size(), v) == 0 โ†’ u.starts_with(v)
  • u.substr(0, v.size()) == v โ†’ u.starts_with(v)
  • v != u.substr(0, v.size()) โ†’ !u.starts_with(v)
  • u.compare(u.size() - v.size(), v.size(), v) == 0 โ†’ u.ends_with(v)
  • u.rfind(v) == u.size() - v.size() โ†’ u.ends_with(v)

๐ŸŒˆ modernize-use-std-bit ๐ŸŒˆ

Okay so this one hunts down the little hand-rolled bit-twiddling idioms we all wrote before 2020 and swaps them for the actual standard functions from the <bit> C++20 header. It's giving "why are you doing math when the library already did the math" ๐Ÿ’…

Covered scenarios:

  • x && !(x & (x - 1)) โ†’ std::has_single_bit(x)
  • (x != 0) && !(x & (x - 1)) โ†’ std::has_single_bit(x)
  • (x > 0) && !(x & (x - 1)) โ†’ std::has_single_bit(x)
  • std::bitset<N>(x).count() โ†’ std::popcount(x)
  • x << 3 | x >> 61 โ†’ std::rotl(x, 3)
  • x << 61 | x >> 3 โ†’ std::rotr(x, 3)

โœจ Options โœจ

  • HonorIntPromotion โ€” when you set this to true (default is false, js), the check inserts an explicit cast so the type of the substituted expression is literally unchanged. No sneaky type drift, periodt.

Example:

// Return type is deduced as 'int' (not 'unsigned char') due to implicit conversions.
auto foo(unsigned char x) {
  return x << 3 | x >> 5;
}

Becomes:

#include <bit>

auto foo(unsigned char x) {
  return static_cast<int>(std::rotl(x, 3));
}

๐Ÿฆ‹ modernize-use-std-format ๐Ÿฆ‹

This icon converts calls to absl::StrFormat โ€” or whatever other functions you point it at via the config options โ€” into C++20's std::format, or into another function via a config option. It rewrites the format string appropriately and yeets the now-unnecessary calls to std::string::c_str() and std::string::data(). Obsessed with the tidiness honestly.

For example, it turns lines like

return absl::StrFormat("The %s is %3d", description.c_str(), value);

into:

return std::format("The {} is {:3}", description, value);

The check uses the same format-string-conversion algorithm as modernize-use-std-print, and its shortcomings plus its behaviour when combined with macros are described in the documentation for that check. So like, go read that one if you want the fine print, iykyk ๐Ÿ“–

โœจ Options โœจ

  • StrictMode โ€” when true, the check will add casts when converting from variadic functions and when printing signed or unsigned integer types (including the fixed-width integer types from <cstdint>, plus ptrdiff_t, size_t and ssize_t) as the opposite signedness, so the output matches what a simple wrapper for std::sprintf that accepted a C-style variable argument list would've produced. Very "trust me I'm preserving semantics" of it.

For example, with StrictMode enabled,

extern std::string strprintf(const char *format, ...);
int i = -42;
unsigned int u = 0xffffffff;
return strprintf("%u %d\n", i, u);

would be converted to

return std::format("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));

to ensure the output continues to be the unsigned representation of -42 and the signed representation of 0xffffffff (often 4294967254 and -1 respectively ๐Ÿ˜ญ). When false (which is the default), those casts will not be added, which may cause a change in the output. Careful, bestie.

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 regular expressions matching the (fully qualified) names of functions 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 is absl::StrFormat.
  • ReplacementFormatFunction โ€” the function that gets used to replace the function set by the StrFormatLikeFunctions option instead of the default std::format. It's expected that the function provides an interface compatible with std::format. A suitable candidate would be fmt::format. ๐Ÿ‘‘
  • FormatHeader โ€” the header that must be included for the declaration of ReplacementFormatFunction, so that a #include directive can be added if required. If ReplacementFormatFunction is std::format then this option defaults to <format>; otherwise this option defaults to nothing and no #include directive will be added.

โญ modernize-use-std-numbers โญ

This one finds constants and function calls to math functions that can be replaced with C++20's mathematical constants from the numbers header, and offers fix-it hints. Chef's kiss. It does not match the use of variables with that value โ€” instead, it offers a replacement for the definition of those variables. Function calls that match the pattern of how the constant is calculated get matched and replaced with the std::numbers constant. And the use of macros gets replaced with the corresponding std::numbers constant, instead of changing the macro definition. Respectful. We stan.

The following list of constants from the numbers header are supported:

  • e
  • log2e
  • log10e
  • pi
  • inv_pi
  • inv_sqrtpi
  • ln2
  • ln10
  • sqrt2
  • sqrt3
  • inv_sqrt3
  • egamma
  • phi

The list currently includes all constants as of C++20. All of them, no crumbs. ๐ŸŒธ

The replacements use the type of the matched constant and can remove explicit casts, i.e., switching between std::numbers::e, std::numbers::e_v<float> and std::numbers::e_v<long double> where appropriate.

double sqrt(double);
double log2(double);
void sink(auto&&) {}
void floatSink(float);

#define MY_PI 3.1415926

void foo() {
    const double Pi = 3.141592653589;           // const double Pi = std::numbers::pi
    const auto Use = Pi / 2;                    // no match for Pi
    static constexpr double Euler = 2.7182818;  // static constexpr double Euler = std::numbers::e;

    log2(exp(1));                               // std::numbers::log2e;
    log2(Euler);                                // std::numbers::log2e;
    1 / sqrt(MY_PI);                            // std::numbers::inv_sqrtpi;
    sink(MY_PI);                                // sink(std::numbers::pi);
    floatSink(MY_PI);                           // floatSink(std::numbers::pi);
    floatSink(static_cast<float>(MY_PI));       // floatSink(std::numbers::pi_v<float>);
}

โœจ Options โœจ

  • DiffThreshold โ€” a floating point value that sets the detection threshold for when literals match a constant. A literal matches a constant if abs(literal - constant) < DiffThreshold evaluates to true. Default is 0.001. ๐Ÿ’–
  • IncludeStyle โ€” a string specifying which include-style is used, either llvm or google. Default is llvm. โœจ

๐Ÿ’… modernize-use-std-print ๐Ÿ’…

Okay so this check converts calls to printf, fprintf, absl::PrintF and absl::FPrintF into equivalent calls to C++23's std::print or std::println as appropriate, modifying the format string appropriately. The replaced and replacement functions can be customised by configuration options. Each argument that is the result of a call to std::string::c_str() and std::string::data() will have that now-unnecessary call removed in a similar manner to the readability-redundant-string-cstr check. ๐Ÿซถ

In other words, it turns lines like:

fprintf(stderr, "The %s is %3d\n", description.c_str(), value);

into:

std::println(stderr, "The {} is {:3}", description, value);

If the ReplacementPrintFunction or ReplacementPrintlnFunction options are left at or set to their default values then this check is only enabled with -std=c++23 or later. ๐Ÿ”ฅ

Macros starting with PRI and __PRI from <inttypes.h> are expanded, escaping is handled and adjacent strings are concatenated to form a single StringLiteral before the format string is converted. Use of any other macros in the format string will cause a warning message to be emitted and no conversion will be performed. The converted format string will always be a single string literal. ๐Ÿ“

The check doesn't do a bad job, but it's not perfect. In particular:

  • It assumes that the format string is correct for the arguments. If you get any warnings when compiling with -Wformat then misbehaviour is possible. ๐Ÿ˜ฌ
  • At the point that the check runs, the AST contains a single StringLiteral for the format string where escapes have been expanded. The check tries to reconstruct escape sequences, they may not be the same as they were written (e.g. "\x41\x0a" will become "A\n" and "ab" "cd" will become "abcd".) ๐Ÿ”
  • It supports field widths, precision, positional arguments, leading zeros, leading +, alignment and alternative forms. ๐Ÿ‘‘
  • Use of any unsupported flags or specifiers will cause the entire statement to be left alone and a warning to be emitted. Particular unsupported features are:
    • The %' flag for thousands separators.
    • The glibc extension %m.
  • printf and similar functions return the number of characters printed. std::print does not. This means that any invocations that use the return value will not be converted. Unfortunately this currently includes explicitly-casting to void. Deficiencies in this check mean that any invocations inside GCC compound statements cannot be converted even if the resulting value is not used. ๐Ÿ’€

If conversion would be incomplete or unsafe then the entire invocation will be left unchanged. ๐Ÿ™ˆ

If the call is deemed suitable for conversion then:

  • printf, fprintf, absl::PrintF, absl::FPrintF and any functions specified by the PrintfLikeFunctions option or FprintfLikeFunctions are replaced with the function specified by the ReplacementPrintlnFunction option if the format string ends with \n or ReplacementPrintFunction otherwise. ๐Ÿ”
  • the format string is rewritten to use the std::formatter language. If a \n is found at the end of the format string not preceded by r then it is removed and ReplacementPrintlnFunction is used rather than ReplacementPrintFunction. โœ‚๏ธ
  • any arguments that corresponded to %p specifiers that std::formatter wouldn't accept are wrapped in a static_cast to const void *. ๐ŸŽฏ
  • any arguments that corresponded to %s specifiers where the argument is of signed char or unsigned char type are wrapped in a reinterpret_cast<const char *>. ๐ŸŽ€
  • any arguments where the format string and the parameter differ in signedness will be wrapped in an appropriate static_cast if StrictMode is enabled. โš–๏ธ
  • any arguments that end in a call to std::string::c_str() or std::string::data() will have that call removed. ๐Ÿงน

๐Ÿ’œ Options ๐Ÿ’œ

  • StrictMode โ€” when true, the check will add casts when converting from variadic functions like printf and printing signed or unsigned integer types (including fixed-width integer types from <cstdint>, ptrdiff_t, size_t and ssize_t) as the opposite signedness to ensure that the output matches that of printf. This does not apply when converting from non-variadic functions such as absl::PrintF and fmt::printf. For example, with StrictMode enabled:
int i = -42;
unsigned int u = 0xffffffff;
printf("%u %d\n", i, u);

would be converted to:

std::print("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));

to ensure that the output will continue to be the unsigned representation of -42 and the signed representation of 0xffffffff (often 4294967254 and -1 respectively.) When false (which is the default), these casts will not be added which may cause a change in the output. ๐Ÿ˜ญ

  • PrintfLikeFunctions โ€” a semicolon-separated list of regular expressions matching the (fully qualified) names of functions 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 nor FprintfLikeFunctions are set then the default value is printf; absl::PrintF, otherwise it is the empty string. ๐Ÿงš
  • FprintfLikeFunctions โ€” a semicolon-separated list of regular expressions matching the (fully qualified) names of functions 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 nor PrintfLikeFunctions are set then the default value is fprintf;absl::FPrintF, otherwise it is the empty string. ๐ŸŒธ
  • ReplacementPrintFunction โ€” the function that will be used to replace printf, fprintf etc. during conversion rather than the default std::print when the original format string does not end with \n. It is expected that the function provides an interface that is compatible with std::print. A suitable candidate would be fmt::print. ๐Ÿ–จ๏ธ
  • ReplacementPrintlnFunction โ€” the function that will be used to replace printf, fprintf etc. during conversion rather than the default std::println when the original format string ends with \n. It is expected that the function provides an interface that is compatible with std::println. A suitable candidate would be fmt::println. ๐Ÿ“ 
  • PrintHeader โ€” the header that must be included for the declaration of ReplacementPrintFunction so that a #include directive can be added if required. If ReplacementPrintFunction is std::print then this option will default to <print>, otherwise this option will default to nothing and no #include directive will be added. ๐Ÿ“š

๐ŸŒˆ modernize-use-string-view ๐ŸŒˆ

Looks for functions returning std::[w|u8|u16|u32]string and suggests to change it to std::[...]string_view for performance reasons if possible. โšก

Each time a new std::string is created from a literal, a copy of that literal is allocated either in std::string's internal buffer (for short literals) or on the heap. ๐Ÿง 

For the cases where std::string is returned from a function, such allocations can sometimes be eliminated by using std::string_view as a return type. โœจ

This check looks for such functions returning std::string constructed from the literals and suggests replacing their return type to std::string_view. ๐ŸŽฏ

It handles std::string, std::wstring, std::u8string, std::u16string and std::u32string along with their aliases and selects the proper kind of std::string_view to return. ๐Ÿฆ‹

Consider the following example:

std::string foo(int i) {
  switch(i) {
    case 1:
      return "case 1";
    ...
    default:
      return "default";
  }
}

In the code above a new std::string object is created on each function invocation, making a copy of a string literal and possibly allocating a memory on the heap. ๐Ÿ˜ญ

The check gets this code transformed into:

std::string_view foo(int i) {
  switch(i) {
    case 1:
      return "case 1";
    ...
    default:
      return "default";
  }
}

New version re-uses statically allocated literals without additional overhead. ๐ŸŽ‰

๐Ÿซถ Suppressing diagnostic ๐Ÿซถ

To prevent an undesired diagnostic wrap the string literal with an explicit std::string(...) constructor:

std::string foo() {
  return "default"; //warning and fix are generated
}

std::string bar() {
  return std::string("default"); //warning and fix are NOT generated
}

๐Ÿ˜ญ Limitations ๐Ÿ˜ญ

  • No warning and/or fix are generated as for now for these code patterns:
    • return std::string("literal");
    • return std::string{"literal"};
    • return "simpleLiteral"s;
    • auto foo() { return "autoReturn"; }
    • auto Trailing() -> std::string { return "Trailing"; } warns, doesn't fix
    • returnings from lambda
    • complicated macro and templated code
    • overloaded functions and methods

In some cases the fixed code will not compile due to lack of conversion from std::string_view to std::string. It can be fixed (preferably) by converting receiver std::string to std::string_view if possible or simply make an explicit conversion. ๐Ÿ› ๏ธ

string foo() {        // <--- will be replaced with string_view
  return "foo";
}

void bar() {
  string err = foo(); // <----- error: no viable conversion from
                      // 'std::string_view' (aka 'basic_string_view<char>')
                      // to 'std::string' (aka 'basic_string<char>')

  string fix(foo());  // <----- no errors
}
  • CheckOverloadedFunctions โ€” bestie, flip this to true and the check will also look at overloaded functions when it's cooking up string_view conversion suggestions โœจ. Leave it false and overloaded functions get skipped, because ambiguous conversions are drama nobody needs ๐Ÿ’€.

Default is false.

  • IgnoredFunctions โ€” a semicolon-separated list of function or method names to ignore, no cap. Regular expressions are accepted, e.g. [Rr]ef(erence)?$ matches every type with suffix Ref, ref, Reference and reference ๐ŸŽ€.

If a name in the list contains the sequence :: it is matched against the qualified type name (i.e. namespace::Type), otherwise it is matched against only the type name (i.e. Type).

The default is toString$;ToString$;to_string$.

  • ReplacementStringViewClass โ€” a semicolon-separated list of string=string_view pairs for replacing string to string_view counterparts ๐Ÿ’…. Here's the whole lineup, key by key:
    • string โ†’ example value llvm::StringRef, default value std::string_view
    • wstring โ†’ example value boost::wstring_view, default value std::wstring_view
    • u8string โ†’ example value absl::u8string_view, default value std::u8string_view
    • u16string โ†’ example value QStringView, default value std::u16string_view
    • u32string โ†’ example value std::u32zstring_view, default value std::u32string_view

๐ŸŒธ modernize-use-structured-binding ๐ŸŒธ

okay this one is ICONIC โ€” it finds places where structured bindings could be used to decompose pairs and suggests replacing them ๐Ÿซถ. Clearer, more idiomatic C++17 code? We love to see it.

This check finds three code patterns and recommends using structured bindings for clearer, more idiomatic C++17 code โœจ.

  1. Decompose a pair variable by assigning its members to separate variables right after its definition:
auto p = getPair<int, int>();
int x = p.first;
int y = p.second;

into:

auto [x, y] = getPair<int, int>();
  1. Use std::tie to decompose a pair into two predefined variables:
int a;
int b;
std::tie(a, b) = getPair<int, int>();

into:

auto [a, b] = getPair<int, int>();
  1. Manually decompose a pair by assigning to its members to local variables in a range-based for loop:
for (auto p : vecOfPairs) {
  int x = p.first;
  int y = p.second;
  // ...
}

into:

for (auto [x, y] : vecOfPairs) {
  // use x and y
}

Limitations

heads up bestie, there are limits ๐Ÿ‘€ The check currently ignores variables defined with attributes or qualifiers except const and & since it's not very common:

static auto pair = getPair();
static int b = pair.first;
static int c = pair.second;

The check doesn't handle some situations which could possibly be transferred to structured bindings, for example:

const auto& results = mapping.try_emplace("hello!");
const iterator& it = results.first;
bool succeed = results.second;
// succeed is not changed in the following code

and:

const auto results = mapping.try_emplace("hello!");
if (results.second) {
    handle_inserted(results.first);
}

๐Ÿ”ฅ modernize-use-trailing-return-type ๐Ÿ”ฅ

Rewrites function and lambda signatures to use a trailing return type (introduced in C++11) ๐Ÿ’œ. And listen โ€” this transformation is purely stylistic, fr fr. The return type before the function name is replaced by auto and inserted after the function parameter list (and qualifiers). Slay ๐Ÿ’….

Example

int f1();
inline int f2(int arg) noexcept;
virtual float f3() const && = delete;
auto lambda = []() {};

transforms to:

auto f1() -> int;
inline auto f2(int arg) -> int noexcept;
virtual auto f3() const && -> float = delete;
auto lambda = []() -> void {};

Limitations

The following categories of return types cannot be rewritten currently ๐Ÿ˜ญ:

  • function pointers
  • member function pointers
  • member pointers

Unqualified names in the return type might erroneously refer to different entities after the rewrite โš ๏ธ. Preventing such errors requires a full lookup of all unqualified names present in the return type in the scope of the trailing return type location. This location includes e.g. function parameter names and members of the enclosing class (including all inherited classes). Such a lookup is currently not implemented. not me crying at the incomplete lookup ๐Ÿ’€.

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. so close bestie, so close ๐ŸŽ€

Options

  • TransformFunctions โ€” when set to true, function declarations will be transformed to use trailing return. Default is true โœจ.
  • TransformLambdas โ€” controls how lambda expressions are transformed to use trailing return type ๐Ÿฆ‹. Possible values are:
    • all โ€” Transform all lambda expressions without an explicit return type to use trailing return type. If type can not be deduced, auto will be used since C++14 and generic message will be emitted otherwise.
    • all_except_auto โ€” Transform all lambda expressions except those whose return type can not be deduced.
    • none โ€” Do not transform any lambda expressions.

Default is all.


๐Ÿ’– modernize-use-transparent-functors ๐Ÿ’–

Prefer transparent functors to non-transparent ones โ€” the glow-up is real. When using transparent functors, the type does not need to be repeated. The code is easier to read, maintain and less prone to errors. It is 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 untouched to preserve the semantics:

// Non-transparent functor
std::map<const char *, std::string, std::greater<std::string>> s;

Options

  • SafeMode โ€” if the option is set to true, the check will not diagnose cases where using a transparent functor cannot be guaranteed to produce identical results as the original code. The default value for this option is false.
This check requires using C++14 or higher to run โญ โ€” no C++14, no vibes.

๐Ÿฆ‹ modernize-use-uncaught-exceptions ๐Ÿฆ‹

This check will warn on calls to std::uncaught_exception and replace them with calls to std::uncaught_exceptions, since std::uncaught_exception was deprecated in C++17 ๐ŸŒˆ. Sad but true, bestie.

Below are a few examples of what kind of occurrences will be found and what they will be replaced with ๐Ÿ’œ.

#define MACRO1 std::uncaught_exception
#define MACRO2 std::uncaught_exception

int uncaught_exception() {
  return 0;
}

int main() {
  int res;

  res = uncaught_exception();
  // No warning, since it is not the deprecated function from namespace std

  res = MACRO2();
  // Warning, but will not be replaced

  res = std::uncaught_exception();
  // Warning and replaced

  using std::uncaught_exception;
  // Warning and replaced

  res = uncaught_exception();
  // Warning and replaced
}

After applying the fixes the code will look like the following โœจ:

#define MACRO1 std::uncaught_exception
#define MACRO2 std::uncaught_exception

int uncaught_exception() {
  return 0;
}

int main() {
  int res;

  res = uncaught_exception();

  res = MACRO2();

  res = std::uncaught_exceptions();

  using std::uncaught_exceptions;

  res = uncaught_exceptions();
}

๐ŸŽ€ modernize-use-using ๐ŸŽ€

The check converts the usage of typedef with the using keyword ๐Ÿ’…. it's giving a total glow-up.

Before:

typedef int variable;

class Class{};
typedef void (Class::* MyPtrType)() const;

typedef struct { int a; } R_t, *R_p;

After:

using variable = int;

class Class{};
using MyPtrType = void (Class::*)() const;

using R_t = struct { int a; };
using R_p = R_t*;

The checker ignores typedef within extern "C" { ... } blocks ๐Ÿ™ˆ:

extern "C" {
  typedef int InExternC; // Left intact.
}
This check requires using C++11 or higher to run โญ

Options

  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true.
  • IgnoreExternC โ€” if set to true, the check will not give warning inside extern "C" scope. Default is true.

๐ŸŒŠ mpi-buffer-deref ๐ŸŒŠ

This check verifies if a buffer passed to an MPI (Message Passing Interface) function is sufficiently dereferenced ๐Ÿ‘‘. Buffers should be passed as a single pointer or array. As MPI function signatures specify void * for their buffer types, insufficiently dereferenced buffers can be passed, like for example as double pointers or multidimensional arrays, without a compiler warning emitted ๐Ÿ’€.

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 ๐ŸŒˆ

Okay so this one is the vibe check for your MPI calls ๐Ÿ’… โ€” it goes through the MPI functions you're using and checks whether the buffer type actually matches the MPI datatype you handed it. Every single MPI datatype defined by the MPI standard (3.1) gets verified by this check. What gets skipped during verification? User-defined typedefs, custom MPI datatypes, and null pointer constants โ€” those just slide on through untouched bestie ๐Ÿซถ.

Example time:

          // 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);

The first one is giving perfect match energy โœจ the second one is a whole mess and this check will call it out ๐Ÿ˜ญ.

objc-assert-equals ๐ŸŽ€

This one catches improper usages of XCTAssertEqual and XCTAssertNotEqual and swaps them out for XCTAssertEqualObjects or XCTAssertNotEqualObjects.

Why do we care? Because it makes your tests less fragile, periodt. Sooo many of them are improperly relying on pointer equality for strings that actually have equal values โ€” and the language does not guarantee that assumption, no cap ๐Ÿ’€.

objc-avoid-nserror-init ๐Ÿฆ‹

Spots improper initialization of NSError objects.

Per Apple's developer docs, you should always use the factory method errorWithDomain:code:userInfo: to create new NSError objects instead of [NSError alloc] init]. Otherwise you get a warning message at runtime โ€” and nobody wants that for themselves ๐Ÿ™ˆ.

The corresponding info 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. The category implementation will override any -dealloc in the class implementation, which can potentially cause issues โ€” and by issues I mean chaos ๐Ÿ’€.

Classes implement -dealloc to perform important actions to deallocate an object. If a category on the class implements -dealloc, it overrides the class's implementation and unexpected deallocation behavior may occur. Not it. Not the vibe. ๐Ÿ™ˆ

objc-forbidden-subclassing ๐Ÿ‘‘

Finds Objective-C classes that are subclasses of classes which were literally not designed to be subclassed. By default it ships with a list of Objective-C classes that 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 @interface declarations, so the compiler itself prevents others from subclassing your Objective-C classes. See https://clang.llvm.org/docs/AttributeReference.html#objc-subclassing-restricted โ€” icon behavior, fr fr.

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's documentation is very clear that objects which are equal must have the same hash value: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc

Note that this check only verifies the presence of -hash in scenarios where omitting it could result in unexpected behavior. Verifying the actual implementation of -hash? That's on you, the developer โ€” e.g. by adding unit tests to verify the implementation. The check is not doing your homework bestie ๐Ÿ˜ญ

objc-nsdate-formatter ๐Ÿงš

When NSDateFormatter is used to convert an NSDate type to a String type, you can specify a custom format string. Certain format specifiers are undesirable even though they're perfectly legal. See http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns for all legal date patterns ๐Ÿ‘€

This checker reports the following string patterns in a date format specifier as warnings:

  1. yyyy + ww : calendar year specified with week of a week year (unless YYYY is 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) โœ…
  2. F without ee/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)
  3. F without MM : numeric day of week in a month without month.
    • Example: Input Date: 29 December 2014; Format String: F-EE Output string: 5-Mon (Wrong because it reads as 5th Mon of ___ in English)
  4. WW without MM : week of the month without the month.
    • Example: Input Date: 29 December 2014; Format String: WW-yyyy Output string: 05-2014 (Wrong because it reads as 5th Week of ___ in English)
  5. YYYY + QQ : week year specified with quarter of normal year (unless yyyy is also specified).
    • Example 1: Input Date: 29 December 2014; Format String: YYYY-QQ Output 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) โœ…
  6. YYYY + MM : week year specified with Month of a calendar year (unless yyyy is also specified).
    • Example 1: Input Date: 29 December 2014; Format String: YYYY-MM Output 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) โœ…
  7. YYYY + DD : week year with day of a calendar year (unless yyyy is also specified).
    • Example 1: Input Date: 29 December 2014; Format String: YYYY-DD Output 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) โœ…
  8. YYYY + WW : week year with week of a calendar year (unless yyyy is also specified).
    • Example 1: Input Date: 29 December 2014; Format String: YYYY-WW Output 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) โœ…
  9. YYYY + F : week year with day of week in a calendar month (unless yyyy is also specified).
    • Example 1: Input Date: 29 December 2014; Format String: YYYY-ww-F-EE Output 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) โœ…

objc-nsinvocation-argument-lifetime ๐Ÿ’œ

Finds calls to NSInvocation methods under ARC that don't have proper argument object lifetimes. When you pass Objective-C objects as parameters to the NSInvocation methods getArgument:atIndex: and getReturnValue:, the values are copied by value into the argument pointer โ€” which leads to incorrect releasing behavior if the object pointers aren't declared __unsafe_unretained. Big yikes ๐Ÿ˜ญ

For code:

          id arg;
          [invocation getArgument:&arg atIndex:2];

          __strong id returnValue;
          [invocation getReturnValue:&returnValue];

The fix will be:

          __unsafe_unretained id arg;
          [invocation getArgument:&arg atIndex:2];

          __unsafe_unretained id returnValue;
          [invocation getReturnValue:&returnValue];

The check will warn on being passed instance variable references that have lifetimes other than __unsafe_unretained, but it does not propose a fix:

          // "id _returnValue" is declaration of instance variable of class.
          [invocation getReturnValue:&self->_returnValue];

objc-property-declaration ๐ŸŽ€

Finds property declarations in Objective-C files that don't follow the pattern of property names in Apple's programming guide. The property name should be in Lower Camel Case format, okurrr.

For code:

          @property(nonatomic, assign) int LowerCamelCase;

The fix will be:

          @property(nonatomic, assign) int lowerCamelCase;

Heads up though: the check will only fix 'CamelCase' to 'camelCase'. In some other cases we only provide warning messages, because the property name could be complicated. Users will need to come up with a proper name on their own โ€” the check is not your ghostwriter bestie ๐Ÿ’…

This check also accepts special acronyms as prefixes or suffixes. Such prefixes or suffixes suppress the Lower Camel Case check according to the guide: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-1002931-BBCFHEAB

For a full list of well-known acronyms: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE

The corresponding style rule: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-1001757

The check will also accept a property declared in a category with a prefix of lowercase letters followed by a '_' to avoid naming conflict. For example:

          @property(nonatomic, assign) int abc_lowerCamelCase;

The corresponding style rule: https://developer.apple.com/library/content/qa/qa1908/_index.html

objc-super-self โญ

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 super common programmer error โ€” happens when the programmer's original intent was actually 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

Okay so this one is genuinely important, bestie ๐Ÿ’… โ€” it analyzes OpenMP Structured Blocks and checks that no exception ever escapes out of the Structured Block it was thrown in. Like, no exception slipping out the back door, periodt.

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. ๐Ÿ˜ญ Yeah. Undefined. Not cute.

FIXME: this check does not model SEH, setjmp/longjmp. (so like, known limitation, not being shady about it)
WARNING! This check may be expensive on large source files. ๐Ÿ’€ Your compile time is gonna feel it, fr fr.

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

This one finds OpenMP directives that are allowed to contain a default clause, but either don't specify it at all, or specify it with a kind other than none โ€” and then it suggests you use the default(none) clause instead. ๐Ÿซถ

Why tho? Because using default(none) forces developers to explicitly specify data sharing attributes for the variables referenced in the construct โ€” thus making it absolutely obvious which variables are referenced and what their data sharing attribute is. Iconic for readability, and possibly makes errors easier to spot. ๐Ÿ‘‘

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. It analyzes the values of the enumerators in an enum or enum class, including signed values, to recommend the smallest possible underlying type that can represent all the values of the enum. The suggested underlying types are the integral types std::uint8_t, std::uint16_t, and std::uint32_t for unsigned types, and std::int8_t, std::int16_t, and std::int32_t for signed types. Using the suggested underlying types can help reduce the memory footprint of the program and improve performance in some cases. ๐Ÿ”ฅ

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. Obsessed, honestly. ๐ŸŽ€

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

The performance-faster-string-find check is an alias, please see performance-prefer-single-overloadsโ€ฆ wait no, bestie, exactly: please see performance-prefer-single-char-overloads for more information. (Just redirecting you, no notes. ๐Ÿ‘‘)


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. ๐Ÿ˜ญ Like babe why are we copying, that's so much extra work.

The check is only applied to loop variables of types that are expensive to copy โ€” which means they are not trivially copyable or have a non-trivial copy constructor or destructor.

To ensure that it is safe to replace the copy with a const reference, the following heuristic is employed:

  1. The loop variable is const qualified.
  2. The loop variable is not const, but only const methods or operators are invoked on it, or it is used as const reference or value argument in constructors or function calls.

Options โœจ

  • WarnOnAllAutoCopies โ€” When true, warns on any use of auto as the type of the range-based for loop variable. Default is false.
  • AllowedTypes โ€” A semicolon-separated list of names of types allowed to be copied in each iteration. Regular expressions are accepted, e.g. [Rr]ef(erence)?$ matches every type with suffix Ref, ref, Reference and reference. The default is empty. If a name in the list contains the sequence ::, it is matched against the qualified type name (i.e. namespace::Type), otherwise it is matched against only the type name (i.e. Type). ๐Ÿซถ

performance-implicit-cast-in-loop

This check has been renamed to performance-implicit-conversion-in-loop. (Old name, who? Never met her. ๐Ÿ’€)


performance-implicit-conversion-in-loop

This warning appears in a range-based loop with a loop variable of const ref type where the type of the variable does not match the one returned by the iterator. This means that an implicit conversion happens, which can for example result in expensive deep copies. ๐Ÿ˜ญ

Example

map<int, vector<string>> my_map;
for (const pair<int, vector<string>>& p : my_map) {}
// The iterator type is in fact pair<const int, vector<string>>, which means
// that the compiler added a conversion, resulting in a copy of the vectors.

The easiest solution is usually to use const auto& instead of writing the type manually. ๐ŸŽ€ Periodt.


performance-inefficient-algorithm

Warns on inefficient use of STL algorithms on associative containers. ๐Ÿฆ‹

Associative containers implement some of the algorithms as methods which should be preferred to the algorithms in the algorithm header. The methods can take advantage of the order of the elements.

std::set<int> s;
auto it = std::find(s.begin(), s.end(), 43);

// becomes

auto it = s.find(43);

std::set<int> s;
auto c = std::count(s.begin(), s.end(), 43);

// becomes

auto c = s.count(43);

performance-inefficient-string-concatenation

This check warns about the performance overhead arising from concatenating strings using the operator+, for instance:

std::string a("Foo"), b("Bar");
a = a + b;

Instead of this structure you should use operator+= or std::string's (std::basic_string) class member function append(). For instance:

std::string a("Foo"), b("Baz");
for (int i = 0; i < 20000; ++i) {
    a = a + "Bar" + b;
}

Could be rewritten in a greatly more efficient way like:

std::string a("Foo"), b("Baz");
for (int i = 0; i < 20000; ++i) {
    a.append("Bar").append(b);
}

And this can be rewritten too:

void f(const std::string&) {}
std::string a("Foo"), b("Baz");
void g() {
    f(a + "Bar" + b);
}

In a slightly more efficient way like:

void f(const std::string&) {}
std::string a("Foo"), b("Baz");
void g() {
    f(std::string(a).append("Bar").append(b));
}

Options

  • StrictMode โ€” when it's false, this check is in full chill mode and only yells at you about inefficient string usage inside loops. Default is false. ๐Ÿซถ

performance-inefficient-vector-operation

okay bestie, this one is out here catching possible inefficient std::vector operations (think push_back, emplace_back ๐Ÿ‘€) that might cause unnecessary memory reallocations. no cap, your vector does not need that drama. ๐Ÿ’€

it can also catch 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 โ€” slay, less reallocating, more living. โœจ

rn, the check only detects these 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), where the type of range_expression can be std::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::vector is considered the main character. ๐Ÿ’…
  • EnableProto โ€” when true, the check will also warn on inefficient operations for proto repeated fields. otherwise, it only warns on inefficient vector operations. Default is false.

performance-move-const-arg

this check is the friend who taps you on the shoulder and goes "girlโ€ฆ what is that" ๐Ÿ˜ญ it warns when:

  • std::move() is called with a constant argument,
  • std::move() is called with an argument of a trivially-copyable type,
  • the result of std::move() is passed as a const reference argument.

in all three cases, the check will suggest a fix that removes the std::move(). like, just take it out, it's not doing anything babes. ๐Ÿงš

here are examples of each of the three cases, iconic of it:

const string s;
return std::move(s);  // Warning: std::move of the const variable has no effect

int x;
return std::move(x);  // Warning: std::move of the variable of a trivially-copyable type has no effect

void f(const string &s);
string s;
f(std::move(s));  // Warning: passing result of std::move as a const reference argument; no move will actually happen

Options

  • CheckTriviallyCopyableMove โ€” if true, enables detection of trivially copyable types that do not have a move constructor. Default is true.
  • CheckMoveToConstRef โ€” if true, enables detection of std::move() passed as a const reference argument. Default is true.

performance-move-constructor-init

cert-oop11-cpp redirects here as an alias for this check. iykyk โœจ

this 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. bestie that's the whole point of the move constructor, you had ONE job ๐Ÿ˜ญ


performance-no-automatic-move

finds local variables that cannot be automatically moved due to constness. the audacity of const, honestly. ๐Ÿ’€

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. so close! ๐ŸŽ€

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 by name, cool by nature. ๐Ÿ”ฅ

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. so likeโ€ฆ think before you click, 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. literally just say the thing ๐Ÿ—ฃ๏ธ


performance-no-int-to-ptr

diagnoses every integer to pointer cast. every single one. it's giving full surveillance. ๐Ÿ‘‘

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. it's not the same pointer, it just has the same outfit. ๐Ÿ™ˆ

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. and honestly? correct behaviour. ๐Ÿ’…

but when provenance concealment is not the goal of the code, but an accident, this example can be rewritten as follows, without using 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

this check flags user-defined destructors marked with noexcept(expr) where expr evaluates to false (but is not a false literal itself). subtle. sneaky. we see you. ๐Ÿ‘€

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 it's a promise, and you gotta keep your promises bestie ๐Ÿ’–


performance-noexcept-move-constructor

flags user-defined move constructors and assignment operators that are not marked with noexcept, or that are marked with noexcept(expr) where expr evaluates to false (but is not a false literal itself).

move constructors of all the types used with STL containers, for example, should be declared noexcept. otherwise STL may choose copy constructors instead โ€” the betrayal ๐ŸŒธ the same is valid for move assignment operations.


performance-noexcept-swap

flags user-defined swap and iter_swap functions not marked with noexcept, or marked with noexcept(expr) where expr evaluates to false (but is not a false literal itself).

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. yes queen, optimise ๐ŸŽ€


performance-prefer-single-char-overloads

optimizes calls to std::string::find() and friends when the needle passed is a single character string literal. the character literal overload is more efficient โ€” it's simply the superior option, no notes. ๐Ÿ’…

examples:

str.find("A");
str += "B";

// becomes

str.find('A');
str += 'B';

this check flags passing strings of size 1 to miscellaneous member functions as well as operator+=. single quotes only, we're not doing the extra work for one (1) character ๐Ÿฆ‹

Options

  • StringLikeClasses โ€” semicolon-separated list of names of string-like classes. by default, only ::std::basic_string and ::std::basic_string_view are considered. within these classes, the check will only consider member functions named find, rfind, find_first_of, find_first_not_of, find_last_of, find_last_not_of, starts_with, ends_with, contains, or operator+=.

performance-string-view-conversions

finds and removes redundant conversions from std::[w|u8|u16|u32]string_view to std::[...]string in call expressions expecting std::[...]string_view. bestie it's already the right type, why are we doing parkour ๐Ÿ˜ญ

Before:

void foo(int p1, std::string_view p2, double p3);
void bar(std::string_view sv) {
    foo(42, std::string(sv), 3.14); // conversion to std::string is
                                    // redundant as std::string_view
                                    // is expected
    foo(42, std::string(sv).c_str(), 2.71); // conversion to std::string and
                                            // then to char* is redundant
                                            // as std::string_view
                                            // is expected
    foo(42, std::string("foo"), 3.14); // conversion to std::string is
                                       // redundant as std::string_view
                                       // is expected
}

After:

void foo(int p1, std::string_view p2, double p3);
void bar(std::string_view sv) {
    foo(42, sv, 3.14);
    foo(42, sv, 2.71);
    foo(42, "foo", 3.14);
}

Please note

pattern std::string(sv).c_str() can be used intentionally to copy the given string up to the null byte. if that's the vibe you're going for, you may use NOLINT or rewrite your code. ๐Ÿ™ˆ

Before:

void foo(int p1, std::string_view p2, double p3);
void bar(std::string_view sv) {
    foo(42, std::string(sv).c_str(), 2.71); // warning is emitted
}

After:

          void foo(int p1, std::string_view p2, double p3);
          void bar(std::string_view sv) {
              std::string s(sv);
              // No warning emitted for the next 2 lines
              foo(42, s.c_str(), 2.71); // explicit std::string variable used
              foo(42, std::string(sv).c_str(), 2.71); // NOLINT(performance-string-view-conversions)
          }

performance-trivially-destructible

Okay bestie, this one is on the hunt for types that could be made trivially-destructible if you'd just delete those out-of-line defaulted destructor declarations. That's it. That's the whole vibe. โœจ

struct A: TrivialType {
  ~A(); // Makes A non-trivially-destructible.
  TrivialType trivial_fields;
};
A::~A() = default;

Likeโ€ฆ it's giving "why are you even here" energy. ๐Ÿซถ


performance-type-promotion-in-math-fn

This check finds calls to C math library functions (from math.h or, in C++, cmath) that have an implicit float โ†’ double promotion going on. ๐Ÿ™ˆ

So for example, it warns on ::sin(0.f), because that function's parameter is a double. You probably meant to call std::sin(0.f) (in C++), or sinf(0.f) (in C). No cap.

float a;
asin(a);

// becomes

float a;
std::asin(a);

performance-unnecessary-copy-initialization

This one finds local variable declarations that get initialized using the copy constructor of a non-trivially-copyable type โ€” when honestly it would've sufficed to just grab a const reference. ๐Ÿ’…

Heads up: the check only fires if it's genuinely safe to swap the copy for a const reference. That's the case when the variable is const qualified, or when it's only used as a const โ€” meaning only const methods or operators get invoked on it, or it's used as a const reference or value argument in constructors or function calls.

Example, bestie:

const string& constReference();
void Function() {
  // The warning will suggest making this a const reference.
  const string UnnecessaryCopy = constReference();
}

struct Foo {
  const string& name() const;
};
void Function(const Foo& foo) {
  // The warning will suggest making this a const reference.
  string UnnecessaryCopy1 = foo.name();
  UnnecessaryCopy1.find("bar");

  // The warning will suggest making this a const reference.
  string UnnecessaryCopy2 = UnnecessaryCopy1;
  UnnecessaryCopy2.find("bar");
}

Options

  • AllowedTypes โ€” a semicolon-separated list of names of types that are allowed to be initialized by copying. Regular expressions are accepted, e.g. [Rr]ef(erence)?$ matches every type with suffix Ref, ref, Reference and reference. The default is empty. If a name in the list contains the sequence ::, it's matched against the qualified type name (i.e. namespace::Type), otherwise it's matched against only the type name (i.e. Type). โœจ
  • ExcludedContainerTypes โ€” a semicolon-separated list of names of types whose methods are allowed to return the const reference 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 doesn't trigger. This is useful for excluding types that are known to be const-incorrect, or where the lifetime or immutability of the returned references isn't tied to mutations of the container. An example is view types that don't own the underlying data. Like for AllowedTypes above, regular expressions are accepted, and the inclusion of :: determines whether the qualified typename is matched or not. ๐ŸŽ€

Limitations

๐Ÿ˜ญ Big one: this check does not perform lifetime analysis, and it may suggest replacing copies with const references that could become dangling. Be cautious when the referenced object might be invalidated by subsequent operations.
void consume(const S&);

void func(std::vector<S> &Vec) {
  const auto It = Vec.begin();
  const S Value(*It); // The warning will suggest making this a const reference.
  Vec.erase(It); // Container modifications could invalidate references.
  consume(Value); // Safe with copy, dangling reference otherwise.
}

performance-unnecessary-value-param

This check flags value parameter declarations of expensive-to-copy types that get copied on every single invocation โ€” when it would've been totally fine to pass them by const reference. ๐Ÿ’€

The check is only applied to parameters of types that are expensive to copy, which means they're not trivially copyable, or they have a non-trivial copy constructor or destructor.

And to make sure it's actually safe to replace the value parameter with a const reference, here's the heuristic (iconic little checklist, ngl):

  1. the parameter is const qualified;
  2. the parameter is not const, but only const methods or operators are invoked on it, or it's used as a const reference 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);
}

Now, if the parameter is not const, is only copied or assigned once, and has a non-trivial move-constructor or move-assignment operator respectively โ€” the check will suggest you move it instead. Slay. ๐Ÿฆ‹

Example:

void setValue(string Value) {
  Field = Value;
}

Will become:

#include <utility>

void setValue(string Value) {
  Field = std::move(Value);
}
โš ๏ธ Warning time, bestie: because the fix-it needs to change the signature of the function, it may break builds if the function is used in multiple translation units, or if some code depends on function signatures. Lowkey important.

Options

  • IncludeStyle โ€” a string specifying which include-style is used, llvm or google. Default is llvm.
  • 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 suffix Ref, ref, Reference and reference. The default is empty. If a name in the list contains the sequence ::, it's matched against the qualified type name (i.e. namespace::Type), otherwise it's matched against only the type name (i.e. Type).
  • IgnoreCoroutines โ€” a boolean specifying whether the check should suggest passing parameters by reference in coroutines. Passing parameters by reference in coroutines may not be safe, please see cppcoreguidelines-avoid-reference-coroutine-parameters for more information. Default is true.

performance-use-std-move

This check suggests inserting std::move(...) to turn copy assignment operator calls into move assignment ones โ€” when it's deemed valid and profitable. Profit, periodt. ๐Ÿ’ธ

void assign(std::vector<int>& out) {
  std::vector<int> some = make_vector();
  use_vector(some);
  out = some;
}

// becomes

void assign(std::vector<int>& out) {
  std::vector<int> some = make_vector();
  use_vector(some);
  out = std::move(some);
}

portability-avoid-pragma-once

Finds uses of #pragma once and suggests replacing them with standard include guards (#ifndef/#define/#endif) for improved portability. ๐ŸŒˆ

#pragma once is a non-standard extension โ€” yes, even though literally every modern compiler supports it. Relying on it can lead to portability issues in some environments. ๐Ÿ™ˆ

Some older or specialized C/C++ compilers, particularly in embedded systems, may not fully support #pragma once.

It can also fail in certain file system configurations, like network drives or complex symbolic links, potentially leading to compilation issues. Not the vibe.

Consider the following header file:

// my_header.h
#pragma once // warning: avoid 'pragma once' directive; use include guards instead

The warning suggests using include guards:

// my_header.h
#ifndef PATH_TO_MY_HEADER_H // Good: use include guards.
#define PATH_TO_MY_HEADER_H

#endif // PATH_TO_MY_HEADER_H

portability-no-assembler

Checks for assembler statements. Use of inline assembly should be avoided since it ties your code to a specific CPU architecture and syntax, making anything that uses it non-portable across platforms. ๐Ÿ’…

asm("mov al, 2");  // warning: do not use assembler statements

portability-restrict-system-includes

Checks to selectively allow or disallow a configurable list of system headers. Very main character of it. ๐Ÿ‘‘

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 -checks glob list for running clang-tidy itself, 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, then 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 that the intrinsics are non-portable (and that there are P0214 alternatives). ๐Ÿคทโ€โ™€๏ธ

So here's the lore: many architectures provide SIMD operations (e.g. x86 SSE/AVX, Power AltiVec/VSX, ARM NEON). It's 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, like, so many common SIMD operations ๐Ÿ’–. And here's the tea: if you migrate from target-dependent intrinsics over to P0214 operations, your SIMD code gets way simpler and the pieces for different targets can actually be unified. Slay.

If you want the intro and the motivation for the data-parallel standard library, go read P0214. It's giving โœจfoundational textโœจ.


๐ŸŒธ Options ๐ŸŒธ

  • Suggest โ€” set this to true (default is false) and the check will suggest P0214 alternatives for you ๐ŸŽ€. Leave it off and it'll just point out that the intrinsic function is non-portable. That's it. That's the whole vibe check.
  • Std โ€” the namespace used to suggest those P0214 alternatives ๐Ÿ’…. If you don't specify it, it's std:: for -std=c++20 and std::experimental:: for -std=c++11.

๐Ÿฆ‹ portability-std-allocator-const ๐Ÿฆ‹

This one reports use of std::vector<const T> (and similar containers of const elements) ๐Ÿ™ˆ. These are not allowed in standard C++, and should usually be std::vector<T> instead.

Per C++ [allocator.requirements.general]: "T is any cv-unqualified object type", so std::allocator<const T> is undefined ๐Ÿ’€. And since many standard containers use std::allocator by default, their const T instantiations are undefined too. Not a cute look.

libc++ defines std::allocator<const T> as an extension which will be removed in the future โณ.

libstdc++ and MSVC straight up 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.'

So code bases that only ever get compiled with libc++ may lowkey accrue this undefined usage ๐Ÿ˜ญ. This check finds that code and prevents backsliding while the clean-up is ongoing. We love a guardrail. ๐Ÿซถ


๐Ÿงš portability-template-virtual-member-function ๐Ÿงš

Finds cases when an uninstantiated virtual member function in a template class causes cross-compiler incompatibility ๐Ÿ˜ค.

Here's the deal bestie: when you instantiate a template class, non-virtual member functions don't have to be instantiated unless they're used. But virtual member function instantiation? That's unspecified and depends on the implementation of the compiler. Fun.

In the following snippets the virtual member function is not instantiated by GCC and Clang, but it is instantiated by MSVC โ€” so while the snippet is accepted by the former compilers, it's rejected by the latter ๐Ÿ’€:

template<typename T>
struct CrossPlatformError {
    virtual ~CrossPlatformError() = default;

    static void used() {}

    virtual void unused() {
        T MSVCError = this;
    };
};

int main() {
    CrossPlatformError<int>::used();
    return 0;
}

Cross-platform projects that need to support MSVC on Windows might see compiler errors because certain virtual member functions get instantiated, which are not instantiated by other compilers on other platforms ๐ŸŒ. This check highlights those virtual member functions so you can catch them before Windows does. ๐Ÿ”ฅ


๐ŸŽ€ readability-ambiguous-smartptr-reset-call ๐ŸŽ€

Finds potentially erroneous calls to the reset method on smart pointers when the pointee type also has a reset method ๐Ÿ‘€. Having a reset method in both classes makes it easy to accidentally make the pointer null when you were actually intending to reset the underlying object. Classic.

struct Resettable {
  void reset() { /* Own reset logic */ }
};

auto ptr = std::make_unique<Resettable>();

ptr->reset();  // Calls underlying reset method
ptr.reset();   // Makes the pointer null

Both calls are valid C++ code, but the second one might not be what the developer intended โ€” it destroys the pointed-to object rather than resetting its state ๐Ÿ˜ญ. And it's easy to make such a typo because the difference between . and -> is literally so small. Blink and you've deleted your object.

The recommended approach is to make the intent explicit by using either member access or direct assignment:

std::unique_ptr<Resettable> ptr = std::make_unique<Resettable>();

(*ptr).reset();  // Clearly calls underlying reset method
ptr = nullptr;   // Clearly makes the pointer null

The default smart pointers and classes that are considered are std::unique_ptr, std::shared_ptr, boost::shared_ptr ๐Ÿ’œ. To specify other smart pointers or other classes, use the SmartPointers option.

NOTE: the check may emit invalid fix-its and misleading warning messages when you specify custom smart pointers or other classes in the SmartPointers option ๐Ÿ™ˆ. For example, boost::scoped_ptr does not have an operator=, which makes fix-its invalid.
NOTE: automatic fix-its are enabled only if clang-tidy is invoked with the --fix-notes option โญ.

๐Ÿ’… Options ๐Ÿ’…

  • SmartPointers โ€” semicolon-separated list of fully qualified class names of custom smart pointers. Default value is ::std::unique_ptr;::std::shared_ptr; ::boost::shared_ptr.

๐ŸŒˆ readability-avoid-const-params-in-decls ๐ŸŒˆ

Checks whether a function declaration has parameters that are top level const โœจ.

const values in declarations do not affect the signature of a function, so they should not be put there. Periodt.

Examples:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

๐Ÿ“ Options ๐Ÿ“

  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true.

๐Ÿ‘‘ readability-avoid-nested-conditional-operator ๐Ÿ‘‘

Identifies instances of nested conditional operators in the code ๐Ÿ˜ตโ€๐Ÿ’ซ.

Nested conditional operators (aka ternary operators, iykyk) can contribute to reduced code readability and comprehension. So they should be split into several statements, storing the intermediate results in a temporary variable. Your future self will thank you.

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. Not the vibe.

Example:

void g();
void f() {
    // ...
    return g();
}

In a long function body, that return statement suggests the function returns a value. But return g(); is actually a combination of two statements that should be written as

g();
return;

to make it 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. Cute of C.

๐ŸŒธ Options ๐ŸŒธ

  • IgnoreMacros โ€” the value false specifies that return statements expanded from macros are not checked. The default value is true.
  • StrictMode โ€” the value false specifies that a direct return statement shall be excluded from the analysis if it is the only statement not contained in a block, like if (cond) return g();. The default value is true.

๐Ÿฆ‹ readability-avoid-unconditional-preprocessor-if ๐Ÿฆ‹

Finds code blocks that are constantly enabled or disabled in preprocessor directives by analyzing #if conditions โ€” like #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. Which then causes maintenance issues, confusion for future developers, and potential compilation problems. A whole domino situation.

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. Very demure. ๐ŸŽ€


๐Ÿงš readability-braces-around-statements ๐Ÿงš

google-readability-braces-around-statements redirects here as an alias for this check ๐Ÿ’ซ.

Checks that bodies of if statements and loops (for, do while, and while) are inside braces.

Before:

if (condition)
  statement;

After:

if (condition) {
  statement;
}

Look at that. Iconic. ๐Ÿ‘‘

๐Ÿ’œ Options ๐Ÿ’œ

  • ShortStatementLines โ€” defines the minimal number of lines that the statement should have in order to trigger this check ๐Ÿ“. The number of lines is counted from the end of the condition or initial keyword (do/else) until the last line of the inner statement. Default value 0 means that braces will be added to all statements (not having them already).

โญ readability-const-return-type โญ

Checks for functions with a const-qualified return type and recommends removal of the const keyword ๐ŸŽ€. Such use of const is usually superfluous, and can prevent valuable compiler optimizations. Does not (yet) fix trailing return types โ€” noted, don't come for it.

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 to true, the check will not give warnings inside macros. Default is true.

๐Ÿซถ 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. Efficiency! We love her. ๐Ÿ”ฅ

OK so real quick, here's the glow-up chart โ€” left side is what your code looks like now, right side is what it becomes, and honestly it's giving upgrade:

  • myMap.find(x) == myMap.end() โ†’ !myMap.contains(x)
  • myMap.find(x) != myMap.end() โ†’ myMap.contains(x)
  • myStr.find(x) != std::string::npos โ†’ myStr.contains(x)
  • if (myMap.count(x)) โ†’ if (myMap.contains(x))
  • bool exists = myMap.count(x) โ†’ bool exists = myMap.contains(x)
  • bool exists = myMap.count(x) > 0 โ†’ bool exists = myMap.contains(x)
  • bool exists = myMap.count(x) >= 1 โ†’ bool exists = myMap.contains(x)
  • bool missing = myMap.count(x) == 0 โ†’ bool missing = !myMap.contains(x)

This check will apply to any class that has a contains method, notably including std::set, std::unordered_set, std::map, and std::unordered_map as of C++20, and std::string and std::string_view as of C++23. โœจ


readability-container-data-pointer

Bestie. This one catches the cases where you could just call data() instead of doing the whole "address of the element at index 0" thing (&container[0], you know the one ๐Ÿซฃ). That pattern is commonly used to materialize a pointer to the backing data of a container โ€” and like, std::vector and std::string both hand you a data() accessor to retrieve that data pointer, so data() should be preferred. Periodt.

It also ensures that in the case the container is empty, the data pointer access does not perform an errant memory access. ๐Ÿ’€ So likeโ€ฆ safety AND style, we love to see it.

Options

  • IgnoredContainers โ€” Semicolon-separated list of containers regexp for which this check won't be enforced. Default is an empty string.

readability-container-size-empty

Checks whether a call to the size()/length() method or the std::size() free function 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 or std::size(). 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 โ€” yeah, rude of them, but true ๐Ÿ™ˆ. Using empty() whenever possible makes it easier to switch to another container in the future.

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.

Options

  • ExcludedComparisonTypes โ€” A semicolon-separated list of regular expressions matching 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 using empty() 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 โ€” yeah it's a little recursive queen, it keeps finding crumbs. ๐Ÿ“

After making a member function static, you might want to run the check readability-static-accessed-through-instance to replace calls like Instance.method() by Class::method().


readability-delete-null-pointer

Checks the if statements where a pointer's existence is checked and then deletes the pointer. The check is unnecessary as deleting a null pointer has no effect. Like literally no effect at all, so the if is just decoration ๐Ÿ’…

int *p;
if (p)
  delete p;

readability-duplicate-include

Looks for duplicate includes and removes them. The check maintains a list of included files and looks for duplicates. If a macro is defined or undefined then the list of included files is cleared. ๐Ÿงน

Examples:

#include <memory>
#include <vector>
#include <memory>

becomes

#include <memory>
#include <vector>

Because of the intervening macro definitions, this code remains unchanged:

#undef NDEBUG
#include "assertion.h"
// ...code with assertions enabled

#define NDEBUG
#include "assertion.h"
// ...code with assertions disabled

Options

  • IgnoredFilesList โ€” A semicolon-separated list of regular expressions or filenames that are allowed to be included multiple times without diagnostics. Matching is performed against the textual include name. Default is an empty string.

readability-else-after-return

So the LLVM Coding Standards advise reducing indentation where possible, and where it makes understanding code easier. Early exit is one of the suggested enforcements of that. Please do not use else or else if after something that interrupts control flow โ€” like return, break, continue, throw. Once you've bounced out, the else is just extra baggage, ok? ๐Ÿ˜Œ

The following piece of code illustrates how the check works. This piece of code:

void foo(int Value) {
  int Local = 0;
  for (int i = 0; i < 42; i++) {
    if (Value == 1) {
      return;
    } else {
      Local++;
    }

    if (Value == 2)
      continue;
    else
      Local++;

    if (Value == 3) {
      throw 42;
    } else {
      Local++;
    }
  }
}

Would be transformed into:

void foo(int Value) {
  int Local = 0;
  for (int i = 0; i < 42; i++) {
    if (Value == 1) {
      return;
    }
    Local++;

    if (Value == 2)
      continue;
    Local++;

    if (Value == 3) {
      throw 42;
    }
    Local++;
  }
}

Options

  • WarnOnUnfixable โ€” When true, emit a warning for cases where the check can't output a Fix-It. These can occur with declarations inside the else branch that would have an extended lifetime if the else branch was removed. Default value is true.
  • WarnOnConditionVariables โ€” When true, the check will attempt to refactor a variable defined inside the condition of the if statement that is used in the else branch defining them just before the if statement. This can only be done if the if statement is the last statement in its parent's scope. Default value is true.

LLVM alias

There is an alias of this check called llvm-else-after-return. In that version the options WarnOnUnfixable and WarnOnConditionVariables are both set to false by default. Noted. ๐Ÿ“

This check helps to enforce this LLVM Coding Standards recommendation.


readability-enum-initial-value

Enforces consistent style for enumerators' initialization, covering three styles: none, first only, or all initialized explicitly. ๐ŸŒˆ

An inconsistent style and strictness to defining the initializing value of enumerators may cause issues if the enumeration is extended with new enumerators that obtain their integer representation implicitly. Yeah โ€” sneaky implicit numbers, not the vibe.

The following three cases are accepted:

  1. No enumerators are explicit initialized.
  2. Exactly the first enumerator is explicit initialized.
  3. 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 {    // warning: initial values in enum 'D' are not consistent,
            //          consider explicit initialization of all, none or only
            //          the first enumerator
  d0 = 0,
  d1,       // note: uninitialized enumerator 'd1' defined here
  d2 = 2,
};

enum E {    // warning: initial values in enum 'E' are not consistent,
            //          consider explicit initialization of all, none or only
            //          the first enumerator
  e0 = 0,
  e1,       // note: uninitialized enumerator 'e1' defined here
  e2 = 2,
  e3,       // note: uninitialized enumerator 'e3' defined here
            // Dangerous, as the numeric values of e3 and e5 are both 3,
            // and this is not explicitly visible in the code!
  e4 = 2,
  e5,       // note: uninitialized enumerator 'e5' defined here
};
Ok lowkey that enum E one is a jumpscare ๐Ÿ’€ two enumerators silently sharing the value 3? bestie that's a bug factory.

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 to false, the first enumerator must not be explicitly initialized to a literal 0. Default is true.
enum F {
  f0 = 0, // Not allowed if AllowExplicitZeroFirstInitialValue is false.
  f1,
  f2,
};
  • AllowExplicitSequentialInitialValues โ€” set this to false and explicit initialization to sequential values is a no-go. Default is true. ๐Ÿ’…
   enum G {
     g0 = 1, // Not allowed if AllowExplicitSequentialInitialValues is false.
     g1 = 2,
     g2 = 3,

readability-function-cognitive-complexity</p> ๐Ÿง โœจ

Okay so this one checks the function Cognitive Complexity metric. And yes bestie, it's a whole metric, not just a vibe.

The metric is implemented as per the COGNITIVE COMPLEXITY by SonarSource specification version 1.2 (19 April 2017). No cap, that's the actual spec. ๐Ÿ“‹

๐ŸŒธ Options ๐ŸŒธ

  • Threshold โ€” flags functions with Cognitive Complexity exceeding this number. The default is 25. So like, 26+ and it's getting called out. ๐Ÿšฉ
  • DescribeBasicIncrements โ€” set to true and for each function exceeding the complexity threshold, the check will issue additional diagnostics on every piece of code (loop, if statement, etc.) which contributes to that complexity. See also the examples below. Default is true. It's giving "we're not just gonna tell you it's messy, we're gonna point at the mess". ๐Ÿ‘‰
  • IgnoreMacros โ€” set to true and the check will ignore code inside macros. Heads up though: also any macro arguments are ignored, even if they should count to the complexity. As this might change in the future, this option isn't guaranteed to be forward-compatible. Default is false. โš ๏ธ

๐ŸŽ€ Building blocks ๐ŸŽ€

There are three basic building blocks of a Cognitive Complexity metric. Three! Iconic trio behavior. ๐Ÿ’…

โญ Increment

The following structures increase the function's Cognitive Complexity metric (by 1):

  • Conditional operators:
    • if()
    • else if()
    • else
    • cond ? true : false
  • switch()
  • Loops:
    • for()
    • C++11 range-based for()
    • while()
    • do while()
  • catch ()
  • goto LABEL, goto *(&&LABEL)),
  • sequences of binary logical operators:
    • boolean1 || boolean2
    • boolean1 && boolean2

โญ Nesting level

So 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. Sneaky little supporting character. ๐Ÿซถ

The following structures increase the nesting level (by 1):

  • Conditional operators:
    • if()
    • else if()
    • else
    • cond ? true : false
  • switch()
  • Loops:
    • for()
    • C++11 range-based for()
    • while()
    • do while()
  • catch ()
  • Nested functions:
    • C++11 Lambda
    • Nested class
    • Nested struct
  • GNU statement expression
  • Apple Block Declaration

โญ Nesting increment

This is where the previous basic building block, Nesting level, matters. Like, actually matters. 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. Literally flawless, no notes.

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. Look at it go. ๐Ÿ”ฅ

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 the last example, the check will flag function3 if the option Threshold is set to 2 or smaller. If the option DescribeBasicIncrements is set to true, it will additionally flag the two if statements with the amounts by which they increase to the complexity of the function and the current nesting level. Receipts! ๐Ÿงพ

๐Ÿ’€ Limitations

The metric is implemented with two notable exceptions โ€” and yeah, "notable" is doing a lot of work here:

  • 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. Big sad. ๐Ÿ˜ญ

readability-function-size ๐Ÿ“

google-readability-function-size redirects here as an alias for this check. Same check, different fit. ๐Ÿ’ซ

Checks for large functions based on various metrics.

๐ŸŒˆ Options ๐ŸŒˆ

  • LineThreshold โ€” flag functions exceeding this number of lines. The default is none (ignore the number of lines).
  • StatementThreshold โ€” flag functions exceeding this number of statements. This may differ significantly from the number of lines for macro-heavy code. The default is 800.
  • BranchThreshold โ€” flag functions exceeding this number of control statements. The default is none (ignore the number of branches).
  • ParameterThreshold โ€” flag functions that exceed a specified number of parameters. The default is none (ignore the number of parameters).
  • NestingThreshold โ€” flag compound statements which create next nesting level after NestingThreshold. This may differ significantly from the expected value for macro-heavy code. The default is none (ignore the nesting level).
  • VariableThreshold โ€” flag functions exceeding this number of variables declared in the body. The default is none (ignore the number of variables). Please note that function parameters and variables declared in lambdas, GNU Statement Expressions, and nested class inline functions are not counted. Not me forgetting those exist โ€” the check does too. ๐Ÿ™ˆ
  • CountMemberInitAsStmt โ€” when true, count class member initializers in constructors as statements. Default is true.
  • IgnoreMacros โ€” if set to true, the check will not count statements, branches, nesting levels, or variable declarations inside macros. Default is false.

readability-identifier-length ๐ŸŽ€

This check finds variables and function parameters whose length are too short. The desired name length is configurable. Special short names which should be ignored can be specified. Local variables with short names can also be ignored if they are short-lived. Basically: give your variables a name, bestie, they deserve one. ๐Ÿ’–

๐ŸŒธ Options ๐ŸŒธ

The following options are described below:

  • MinimumVariableNameLength, IgnoredVariableNames
  • MinimumBindingNameLength, IgnoredBindingNames
  • MinimumParameterNameLength, IgnoredParameterNames
  • MinimumLoopCounterNameLength, IgnoredLoopCounterNames
  • MinimumExceptionNameLength, IgnoredExceptionVariableNames
  • LineCountThreshold
  • MinimumVariableNameLength โ€” all variables (other than loop counter, 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.
int i = 42;    // warns that 'i' is too short
  • IgnoredVariableNames โ€” specifies a regular expression for variable names that are to be ignored. The default value is empty, thus no names are ignored. Nobody's safe. ๐Ÿ’…
  • MinimumBindingNameLength โ€” all variables introduced by structured bindings are expected to have at least a length of MinimumBindingNameLength (default is 2). Setting it to 0 or 1 disables the check entirely.
auto [a] = get_result();    // warns that 'a' is too short
  • IgnoredBindingNames โ€” specifies a regular expression for variable names introduced by structured bindings that are to be ignored. The default value is ^[_]$, to allow the use of the _ idiom to specify that the value is discarded on purpose. Respect the underscore, it's literally saying "idc". ๐Ÿ™Œ
  • 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;
}
  • IgnoredParameterNames โ€” specifies a regular expression for parameters that are to be ignored. The default value is ^[n]$ for historical reasons. Yeah. Historical reasons. ๐Ÿ•ฐ๏ธ
  • 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 since 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) {
    // ...
}
  • LineCountThreshold โ€” defines the minimum number of lines required between declaration and last use for a diagnostic to be issued. The default value for this option is 0, which corresponds to all variables being flagged. This option only affects the behavior regarding local variables: a warning is always issued when a global variable has a short name, because globals can potentially be used across multiple files. Globals stay flagged, periodt. ๐Ÿ‘‘
// In this example, a warning will be issued if LineCountThreshold < N
int a = 0;      // First line (declaration line)
a = 1;          // Second line
                // ...
last_use_of(a); // N-th line

readability-identifier-naming โœจ

Checks for identifiers naming style mismatch. Like a vibe check but for your variable names. ๐Ÿชž

okay so this check is literally out here trying to enforce your coding guidelines on how you name identifiers โ€” it's giving hall monitor but make it fashion ๐Ÿ’… It supports one of the casing types below and will try to convert from one to another if it catches a mismatch (bestie it will fix your vibe for you).

๐ŸŒธ the casing types, periodt ๐ŸŒธ

  • lower_case
  • UPPER_CASE
  • camelBack
  • CamelCase
  • camel_Snake_Back
  • Camel_Snake_Case
  • aNy_CasE
  • Leading_upper_snake_case

It also supports a fixed prefix and suffix that get prepended or appended to the identifiers โ€” regardless of the casing, so don't come for it about consistency ๐ŸŽ€

There are so many configuration options available, all so you can create different rules for different kinds of identifiers. In general, the rules fall back to a more generic rule if the specific case isn't configured. (iykyk โ€” inheritance but for naming rules โœจ)

Lowkey important: the naming of virtual methods is reported where they occur in the base class, but not where they are overridden, because 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 setting
  • On โ€” example: int iVariable
  • LowerCase โ€” example: int i_Variable
  • CamelCase โ€” example: int IVariable

The check only enforces style on kinds of identifiers which have been configured, so an empty config effectively disables it (no config, no problems, we love a chill queen ๐Ÿฆ‹). The DefaultCase option can be used to enforce style on all kinds of identifiers, then optionally overridden for specific kinds which are desired with a different case.

For example, using values of:

  • DefaultCase of lower_case
  • MacroDefinitionCase of UPPER_CASE
  • TemplateParameterCase of CamelCase

Identifies and transforms names as follows:

Before:

#define macroDefinition
template <typename typenameParameter>
int functionDeclaration(typenameParameter paramVal, int paramCount);

After:

#define MACRO_DEFINITION
template <typename TypenameParameter>
int function_declarations(TypenameParameter param_val, int param_count);

look at that glow-up ๐Ÿ˜ญ๐Ÿ”ฅ


โœจ Options summary โœจ

The available options are summarized below:

๐ŸŽ€ General options

  • AggressiveDependentMemberLookup
  • CheckAnonFieldInParent
  • GetConfigPerFile
  • IgnoreMainLikeFunctions

๐Ÿ’… Specific options

  • DefaultCase, DefaultPrefix, DefaultSuffix, DefaultIgnoredRegexp, DefaultHungarianPrefix
  • AbstractClassCase, AbstractClassPrefix, AbstractClassSuffix, AbstractClassIgnoredRegexp, AbstractClassHungarianPrefix
  • ClassCase, ClassPrefix, ClassSuffix, ClassIgnoredRegexp, ClassHungarianPrefix
  • ClassConstexprCase, ClassConstexprPrefix, ClassConstexprSuffix, ClassConstexprIgnoredRegexp, ClassConstexprHungarianPrefix
  • ClassConstantCase, ClassConstantPrefix, ClassConstantSuffix, ClassConstantIgnoredRegexp, ClassConstantHungarianPrefix
  • ClassMemberCase, ClassMemberPrefix, ClassMemberSuffix, ClassMemberIgnoredRegexp, ClassMemberHungarianPrefix
  • ClassMethodCase, ClassMethodPrefix, ClassMethodSuffix, ClassMethodIgnoredRegexp
  • ConceptCase, ConceptPrefix, ConceptSuffix, ConceptIgnoredRegexp
  • ConstantCase, ConstantPrefix, ConstantSuffix, ConstantIgnoredRegexp, ConstantHungarianPrefix
  • ConstantMemberCase, ConstantMemberPrefix, ConstantMemberSuffix, ConstantMemberIgnoredRegexp, ConstantMemberHungarianPrefix
  • ConstantParameterCase, ConstantParameterPrefix, ConstantParameterSuffix, ConstantParameterIgnoredRegexp, ConstantParameterHungarianPrefix
  • ConstantPointerParameterCase, ConstantPointerParameterPrefix, ConstantPointerParameterSuffix, ConstantPointerParameterIgnoredRegexp, ConstantPointerParameterHungarianPrefix
  • ConstexprFunctionCase, ConstexprFunctionPrefix, ConstexprFunctionSuffix, ConstexprFunctionIgnoredRegexp
  • ConstexprMethodCase, ConstexprMethodPrefix, ConstexprMethodSuffix, ConstexprMethodIgnoredRegexp
  • ConstexprVariableCase, ConstexprVariablePrefix, ConstexprVariableSuffix, ConstexprVariableIgnoredRegexp, ConstexprVariableHungarianPrefix
  • EnumCase, EnumPrefix, EnumSuffix, EnumIgnoredRegexp
  • EnumConstantCase, EnumConstantPrefix, EnumConstantSuffix, EnumConstantIgnoredRegexp, EnumConstantHungarianPrefix
  • FunctionCase, FunctionPrefix, FunctionSuffix, FunctionIgnoredRegexp
  • GlobalConstexprVariableCase, GlobalConstexprVariablePrefix, GlobalConstexprVariableSuffix, GlobalConstexprVariableIgnoredRegexp, GlobalConstexprVariableHungarianPrefix
  • GlobalConstantCase, GlobalConstantPrefix, GlobalConstantSuffix, GlobalConstantIgnoredRegexp, GlobalConstantHungarianPrefix
  • GlobalConstantPointerCase, GlobalConstantPointerPrefix, GlobalConstantPointerSuffix, GlobalConstantPointerIgnoredRegexp, GlobalConstantPointerHungarianPrefix
  • GlobalFunctionCase, GlobalFunctionPrefix, GlobalFunctionSuffix, GlobalFunctionIgnoredRegexp
  • GlobalPointerCase, GlobalPointerPrefix, GlobalPointerSuffix, GlobalPointerIgnoredRegexp, GlobalPointerHungarianPrefix
  • GlobalVariableCase, GlobalVariablePrefix, GlobalVariableSuffix, GlobalVariableIgnoredRegexp, GlobalVariableHungarianPrefix
  • InlineNamespaceCase, InlineNamespacePrefix, InlineNamespaceSuffix, InlineNamespaceIgnoredRegexp
  • LocalConstexprVariableCase, LocalConstexprVariablePrefix, LocalConstexprVariableSuffix, LocalConstexprVariableIgnoredRegexp, LocalConstexprVariableHungarianPrefix
  • LocalConstantCase, LocalConstantPrefix, LocalConstantSuffix, LocalConstantIgnoredRegexp, LocalConstantHungarianPrefix
  • LocalConstantPointerCase, LocalConstantPointerPrefix, LocalConstantPointerSuffix, LocalConstantPointerIgnoredRegexp, LocalConstantPointerHungarianPrefix
  • LocalPointerCase, LocalPointerPrefix, LocalPointerSuffix, LocalPointerIgnoredRegexp, LocalPointerHungarianPrefix
  • LocalVariableCase, LocalVariablePrefix, LocalVariableSuffix, LocalVariableIgnoredRegexp, LocalVariableHungarianPrefix
  • MacroDefinitionCase, MacroDefinitionPrefix, MacroDefinitionSuffix, MacroDefinitionIgnoredRegexp
  • MemberCase, MemberPrefix, MemberSuffix, MemberIgnoredRegexp, MemberHungarianPrefix
  • MethodCase, MethodPrefix, MethodSuffix, MethodIgnoredRegexp
  • NamespaceCase, NamespacePrefix, NamespaceSuffix, NamespaceIgnoredRegexp
  • ParameterCase, ParameterPrefix, ParameterSuffix, ParameterIgnoredRegexp, ParameterHungarianPrefix
  • ParameterPackCase, ParameterPackPrefix, ParameterPackSuffix, ParameterPackIgnoredRegexp
  • PointerParameterCase, PointerParameterPrefix, PointerParameterSuffix, PointerParameterIgnoredRegexp, PointerParameterHungarianPrefix
  • PrivateMemberCase, PrivateMemberPrefix, PrivateMemberSuffix, PrivateMemberIgnoredRegexp, PrivateMemberHungarianPrefix
  • PrivateMethodCase, PrivateMethodPrefix, PrivateMethodSuffix, PrivateMethodIgnoredRegexp
  • ProtectedMemberCase, ProtectedMemberPrefix, ProtectedMemberSuffix, ProtectedMemberIgnoredRegexp, ProtectedMemberHungarianPrefix
  • ProtectedMethodCase, ProtectedMethodPrefix, ProtectedMethodSuffix, ProtectedMethodIgnoredRegexp
  • PublicMemberCase, PublicMemberPrefix, PublicMemberSuffix, PublicMemberIgnoredRegexp, PublicMemberHungarianPrefix
  • PublicMethodCase, PublicMethodPrefix, PublicMethodSuffix, PublicMethodIgnoredRegexp
  • ScopedEnumConstantCase, ScopedEnumConstantPrefix, ScopedEnumConstantSuffix, ScopedEnumConstantIgnoredRegexp
  • StaticConstexprVariableCase, StaticConstexprVariablePrefix, StaticConstexprVariableSuffix, StaticConstexprVariableIgnoredRegexp, StaticConstexprVariableHungarianPrefix
  • StaticConstantCase, StaticConstantPrefix, StaticConstantSuffix, StaticConstantIgnoredRegexp, StaticConstantHungarianPrefix
  • StaticVariableCase, StaticVariablePrefix, StaticVariableSuffix, StaticVariableIgnoredRegexp, StaticVariableHungarianPrefix
  • StructCase, StructPrefix, StructSuffix, StructIgnoredRegexp
  • TemplateParameterCase, TemplateParameterPrefix, TemplateParameterSuffix, TemplateParameterIgnoredRegexp
  • TemplateTemplateParameterCase, TemplateTemplateParameterPrefix, TemplateTemplateParameterSuffix, TemplateTemplateParameterIgnoredRegexp
  • TypeAliasCase, TypeAliasPrefix, TypeAliasSuffix, TypeAliasIgnoredRegexp
  • TypedefCase, TypedefPrefix, TypedefSuffix, TypedefIgnoredRegexp
  • TypeTemplateParameterCase, TypeTemplateParameterPrefix, TypeTemplateParameterSuffix, TypeTemplateParameterIgnoredRegexp
  • UnionCase, UnionPrefix, UnionSuffix, UnionIgnoredRegexp
  • ValueTemplateParameterCase, ValueTemplateParameterPrefix, ValueTemplateParameterSuffix, ValueTemplateParameterIgnoredRegexp
  • VariableCase, VariablePrefix, VariableSuffix, VariableIgnoredRegexp, VariableHungarianPrefix
  • VirtualMethodCase, VirtualMethodPrefix, VirtualMethodSuffix, VirtualMethodIgnoredRegexp

๐Ÿ’– Options description ๐Ÿ’–

A detailed description of each option is presented below (grab a snack bestie, this is the whole menu ๐Ÿ“):

  • DefaultCase โ€” when defined, the check will ensure all names by default conform to the selected casing.
  • DefaultPrefix โ€” when defined, the check will ensure all names by default will add the prefix with the given value (regardless of casing).
  • DefaultIgnoredRegexp โ€” identifier naming checks won't be enforced for all names by default matching this regular expression.
  • DefaultSuffix โ€” when defined, the check will ensure all names by default will add the suffix with the given value (regardless of casing).
  • DefaultHungarianPrefix โ€” when enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type.
  • AbstractClassCase โ€” when defined, the check will ensure abstract class names conform to the selected casing.
  • AbstractClassPrefix โ€” when defined, the check will ensure abstract class names will add the prefix with the given value (regardless of casing).
  • AbstractClassIgnoredRegexp โ€” identifier naming checks won't be enforced for abstract class names matching this regular expression.
  • AbstractClassSuffix โ€” when you define this, the check makes sure abstract class names get this suffix tacked on (casing doesn't matter, it just slaps it on there) ๐Ÿ’…
  • AbstractClassHungarianPrefix โ€” turn this on and the check makes sure your declared identifier gets a Hungarian notation prefix based on whatever type it is. iykyk โœจ

So like, if you're running with these values:

  • AbstractClassCase of lower_case
  • AbstractClassPrefix of pre_
  • AbstractClassSuffix of _post
  • AbstractClassHungarianPrefix of On

then it spots and/or transforms abstract class names like this:

Before:

class ABSTRACT_CLASS {
public:
  ABSTRACT_CLASS();
};

After:

class pre_abstract_class_post {
public:
  pre_abstract_class_post();
};
  • AggressiveDependentMemberLookup โ€” set this to true and the check will go digging in dependent base classes for dependent member references that need changing. โš ๏ธ Heads up bestie: this can cause errors with template specializations, which is why the default value is false. Don't say I didn't warn you ๐Ÿ˜ญ

For example, running with these values:

  • ClassMemberCase of lower_case

Before:

template <typename T>
struct Base {
  T BadNamedMember;
};

template <typename T>
struct Derived : Base<T> {
  void reset() {
    this->BadNamedMember = 0;
  }
};

After, if AggressiveDependentMemberLookup is false:

template <typename T>
struct Base {
  T bad_named_member;
};

template <typename T>
struct Derived : Base<T> {
  void reset() {
    this->BadNamedMember = 0;
  }
};

After, if AggressiveDependentMemberLookup is true:

template <typename T>
struct Base {
  T bad_named_member;
};

template <typename T>
struct Derived : Base<T> {
  void reset() {
    this->bad_named_member = 0;
  }
};

See the difference? With true it actually goes in and renames the usage too. Slay ๐ŸŽ€

  • CheckAnonFieldInParent โ€” set this to true and fields inside anonymous records (that's anonymous unions and structs, for the uninitiated ๐Ÿซถ) get treated as names in the enclosing scope instead of as public members of the anonymous record, but only for the purpose of name checking.

For example:

class Foo {
private:
  union {
    int iv_;
    float fv_;
  };
};

If CheckAnonFieldInParent is false, you might get warnings that iv_ and fv_ aren't coherent to public member names โ€” because iv_ and fv_ are literally public members of the anonymous union, bestie, the check isn't being weird. But when CheckAnonFieldInParent is true, iv_ and fv_ get treated as private data members of Foo for name-checking purposes, so no warnings get emitted. No cap โœจ

  • ClassCase โ€” when defined, the check makes sure class names conform to the casing you picked.
  • ClassPrefix โ€” when defined, the check makes sure class names get this prefix added (regardless of casing).
  • ClassIgnoredRegexp โ€” identifier naming checks won't be enforced for class names that match this regular expression. The regex said "not today" ๐Ÿ™ˆ
  • ClassSuffix โ€” when defined, the check makes sure class names get this suffix added (regardless of casing).
  • ClassHungarianPrefix โ€” turn it on and the check makes sure your declared identifier has a Hungarian notation prefix based on the declared type ๐Ÿ‘‘

For example, with these values:

  • ClassCase of lower_case
  • ClassPrefix of pre_
  • ClassSuffix of _post
  • ClassHungarianPrefix of On

it identifies and/or transforms class names like so:

Before:

class FOO {
public:
  FOO();
  ~FOO();
};

After:

class pre_foo_post {
public:
  pre_foo_post();
  ~pre_foo_post();
};

Look at the destructor getting the glow-up too. Obsessed ๐Ÿ’–

  • ClassConstexprCase โ€” when defined, the check makes sure class constexpr names conform to the selected casing.
  • ClassConstexprPrefix โ€” when defined, the check makes sure class constexpr names get this prefix added (regardless of casing).
  • ClassConstexprIgnoredRegexp โ€” identifier naming checks won't be enforced for class constexpr names that match this regular expression.
  • ClassConstexprSuffix โ€” when defined, the check makes sure class constexpr names get this suffix added (regardless of casing).
  • ClassConstexprHungarianPrefix โ€” when enabled, the check makes sure your declared identifier has a Hungarian notation prefix based on the declared type.

For example, with these values:

  • ClassConstexprCase of lower_case
  • ClassConstexprPrefix of pre_
  • ClassConstexprSuffix of _post
  • ClassConstexprHungarianPrefix of On

it identifies and/or transforms class constexpr variable names like this:

Before:

class FOO {
public:
  static constexpr int CLASS_CONSTEXPR;
};

After:

class FOO {
public:
  static const int pre_class_constexpr_post;
};
  • ClassConstantCase โ€” when defined, the check makes sure class constant names conform to the selected casing.
  • ClassConstantPrefix โ€” when defined, the check makes sure class constant names get this prefix added (regardless of casing).
  • ClassConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for class constant names that match this regular expression.
  • ClassConstantSuffix โ€” when defined, the check makes sure class constant names get this suffix added (regardless of casing).
  • ClassConstantHungarianPrefix โ€” when enabled, the check makes sure your declared identifier has a Hungarian notation prefix based on the declared type.

For example, with these values:

  • ClassConstantCase of lower_case
  • ClassConstantPrefix of pre_
  • ClassConstantSuffix of _post
  • ClassConstantHungarianPrefix of On

it identifies and/or transforms class constant names like this:

Before:

class FOO {
public:
  static const int CLASS_CONSTANT;
};

After:

class FOO {
public:
  static const int pre_class_constant_post;
};
  • ClassMemberCase โ€” when defined, the check makes sure class member names conform to the selected casing.
  • ClassMemberPrefix โ€” when defined, the check makes sure class member names get this prefix added (regardless of casing).
  • ClassMemberIgnoredRegexp โ€” identifier naming checks won't be enforced for class member names that match this regular expression.
  • ClassMemberSuffix โ€” when defined, the check makes sure class member names get this suffix added (regardless of casing).
  • ClassMemberHungarianPrefix โ€” when enabled, the check makes sure your declared identifier has a Hungarian notation prefix based on the declared type.

For example, with these values:

  • ClassMemberCase of lower_case
  • ClassMemberPrefix of pre_
  • ClassMemberSuffix of _post
  • ClassMemberHungarianPrefix of On

it identifies and/or transforms class member names like this:

Before:

class FOO {
public:
  static int CLASS_CONSTANT;
};

After:

class FOO {
public:
  static int pre_class_constant_post;
};
  • ClassMethodCase โ€” when defined, the check makes sure class method names conform to the selected casing.
  • ClassMethodPrefix โ€” when defined, the check makes sure class method names get this prefix added (regardless of casing).
  • ClassMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for class method names that match this regular expression.
  • ClassMethodSuffix โ€” when defined, the check makes sure class method names get this suffix added (regardless of casing).

For example, with these values:

  • ClassMethodCase of lower_case
  • ClassMethodPrefix of pre_
  • ClassMethodSuffix of _post

it identifies and/or transforms class method names like this:

Before:

class FOO {
public:
  int CLASS_MEMBER();
};

After:

class FOO {
public:
  int pre_class_member_post();
};
  • ConceptCase โ€” when defined, the check makes sure concept names conform to the selected casing.
  • ConceptPrefix โ€” when defined, the check makes sure concept names get this prefix added (regardless of casing).
  • ConceptIgnoredRegexp โ€” identifier naming checks won't be enforced for concept names that match this regular expression.
  • ConceptSuffix โ€” when defined, the check makes sure concept names get this suffix added (regardless of casing).

For example, with these values:

  • ConceptCase of CamelCase
  • ConceptPrefix of Pre
  • ConceptSuffix of Post

it identifies and/or transforms concept names like this:

Before:

template<typename T> concept my_concept = requires (T t) { {t++}; };

After:

template<typename T> concept PreMyConceptPost = requires (T t) { {t++}; };

Concepts getting the full makeover, we love to see it ๐Ÿฆ‹

  • ConstantCase โ€” when defined, the check makes sure constant names conform to the selected casing.
  • ConstantPrefix โ€” when defined, the check makes sure constant names get this prefix added (regardless of casing).
  • ConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for constant names that match this regular expression.
  • ConstantSuffix โ€” when defined, the check makes sure constant names get this suffix added (regardless of casing).
  • ConstantHungarianPrefix โ€” when enabled, the check makes sure your declared identifier has a Hungarian notation prefix based on the declared type.

For example, with these values:

  • ConstantCase of lower_case
  • ConstantPrefix of pre_
  • ConstantSuffix of _post
  • ConstantHungarianPrefix of On

it identifies and/or transforms constant names like this:

Before:

void function() { unsigned const MyConst_array[] = {1, 2, 3}; }

After:

void function() { unsigned const pre_myconst_array_post[] = {1, 2, 3}; }
  • ConstantMemberCase โ€” when defined, the check makes sure constant member names conform to the selected casing.
  • ConstantMemberPrefix โ€” set this and the check makes sure constant member names get that exact prefix slapped on the front (casing of the value doesn't matter, it just goes on) ๐ŸŽ€
  • ConstantMemberIgnoredRegexp โ€” any constant member name that matches this regex? The naming check literally won't touch it. Ignored, unbothered, thriving ๐Ÿ’…
  • ConstantMemberSuffix โ€” set this and constant member names get the given value tacked on the end as a suffix (again, casing of the value is irrelevant).
  • ConstantMemberHungarianPrefix โ€” turn this on and the check makes sure the declared identifier carries a Hungarian notation prefix based on its declared type ๐Ÿ‘‘

So like, say your values are:

  • ConstantMemberCase of lower_case
  • ConstantMemberPrefix of pre_
  • ConstantMemberSuffix of _post
  • ConstantMemberHungarianPrefix of On

Then it'll identify and/or transform constant member names like this:

Before:

class Foo {
  char const MY_ConstMember_string[4] = "123";
}

After:

class Foo {
  char const pre_my_constmember_string_post[4] = "123";
}

Look at that glow-up ๐Ÿ˜ญโœจ


  • ConstantParameterCase โ€” set it and constant parameter names must conform to the casing you picked.
  • ConstantParameterPrefix โ€” set it and constant parameter names get the given prefix added (casing of the value: doesn't matter).
  • ConstantParameterIgnoredRegexp โ€” constant parameter names matching this regex are exempt from the identifier naming checks. No notes. ๐Ÿ™ˆ
  • ConstantParameterSuffix โ€” set it and constant parameter names get the given value suffixed on (regardless of casing).
  • ConstantParameterHungarianPrefix โ€” enable it and the declared identifier must have a Hungarian notation prefix based on its declared type.

Example values, bestie:

  • ConstantParameterCase of lower_case
  • ConstantParameterPrefix of pre_
  • ConstantParameterSuffix of _post
  • ConstantParameterHungarianPrefix of On

Identifies and/or transforms constant parameter names like so:

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 โ€” set it and constant pointer parameter names conform to the chosen casing.
  • ConstantPointerParameterPrefix โ€” constant pointer parameter names get this prefix added (regardless of casing).
  • ConstantPointerParameterIgnoredRegexp โ€” constant pointer parameter names matching this regex skip the naming checks entirely.
  • ConstantPointerParameterSuffix โ€” constant pointer parameter names get this suffix added (regardless of casing).
  • ConstantPointerParameterHungarianPrefix โ€” enabled โ†’ the declared identifier gets a Hungarian notation prefix based on its declared type.

With values of:

  • ConstantPointerParameterCase of lower_case
  • ConstantPointerParameterPrefix of pre_
  • ConstantPointerParameterSuffix of _post
  • ConstantPointerParameterHungarianPrefix of On

It identifies and/or transforms constant pointer parameter names like this:

Before:

void GLOBAL_FUNCTION(int const *CONST_parameter);

After:

void GLOBAL_FUNCTION(int const *pre_const_parameter_post);

  • ConstexprFunctionCase โ€” set it and constexpr function names conform to the selected casing.
  • ConstexprFunctionPrefix โ€” constexpr function names get the given prefix added (casing? irrelevant).
  • ConstexprFunctionIgnoredRegexp โ€” constexpr function names that match this regex are not enforced against. Bye ๐Ÿ‘‹
  • ConstexprFunctionSuffix โ€” constexpr function names get the given suffix added (regardless of casing).

Values, for instance:

  • ConstexprFunctionCase of lower_case
  • ConstexprFunctionPrefix of pre_
  • ConstexprFunctionSuffix of _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 โ€” set it and constexpr method names conform to the selected casing.
  • ConstexprMethodPrefix โ€” constexpr method names get the given prefix added (regardless of casing).
  • ConstexprMethodIgnoredRegexp โ€” constexpr method names matching this regex are left alone by the naming checks.
  • ConstexprMethodSuffix โ€” constexpr method names get the given suffix added (regardless of casing).

Say your values are:

  • ConstexprMethodCase of lower_case
  • ConstexprMethodPrefix of pre_
  • ConstexprMethodSuffix of _post

Identifies and/or transforms constexpr method names like this:

Before:

class Foo {
public:
  constexpr int CST_expr_Method() { return 2; }
}

After:

class Foo {
public:
  constexpr int pre_cst_expr_method_post() { return 2; }
}

  • ConstexprVariableCase โ€” set it and constexpr variable names conform to the selected casing.
  • ConstexprVariablePrefix โ€” constexpr variable names get the given prefix added (regardless of casing).
  • ConstexprVariableIgnoredRegexp โ€” constexpr variable names matching this regex get no enforcement from the naming checks.
  • ConstexprVariableSuffix โ€” constexpr variable names get the given suffix added (regardless of casing).
  • ConstexprVariableHungarianPrefix โ€” enabled โ†’ the declared identifier must have a Hungarian notation prefix based on the declared type ๐Ÿ’ซ

Example values:

  • ConstexprVariableCase of lower_case
  • ConstexprVariablePrefix of pre_
  • ConstexprVariableSuffix of _post
  • ConstexprVariableHungarianPrefix of On

Identifies and/or transforms constexpr variable names as follows:

Before:

constexpr int ConstExpr_variable = MyConstant;

After:

constexpr int pre_constexpr_variable_post = MyConstant;

  • EnumCase โ€” set it and enumeration names conform to the selected casing.
  • EnumPrefix โ€” enumeration names get the given prefix added (regardless of casing).
  • EnumIgnoredRegexp โ€” enumeration names matching this regex are exempt from the naming checks.
  • EnumSuffix โ€” enumeration names get the given suffix added (regardless of casing).

With values like:

  • EnumCase of lower_case
  • EnumPrefix of pre_
  • EnumSuffix of _post

Identifies and/or transforms enumeration names like so:

Before:

enum FOO { One, Two, Three };

After:

enum pre_foo_post { One, Two, Three };

  • EnumConstantCase โ€” set it and enumeration constant names conform to the selected casing.
  • EnumConstantPrefix โ€” enumeration constant names get the given prefix added (regardless of casing).
  • EnumConstantIgnoredRegexp โ€” enumeration constant names matching this regex don't get enforced. Periodt.
  • EnumConstantSuffix โ€” enumeration constant names get the given suffix added (regardless of casing).
  • EnumConstantHungarianPrefix โ€” enabled โ†’ the declared identifier carries a Hungarian notation prefix based on the declared type.

Example values, take notes:

  • EnumConstantCase of lower_case
  • EnumConstantPrefix of pre_
  • EnumConstantSuffix of _post
  • EnumConstantHungarianPrefix of On

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 };

All three got the treatment, no cap ๐Ÿ’–


  • FunctionCase โ€” set it and function names conform to the selected casing.
  • FunctionPrefix โ€” function names get the given prefix added (regardless of casing).
  • FunctionIgnoredRegexp โ€” function names matching this regex are not enforced by the naming checks.
  • FunctionSuffix โ€” function names get the given suffix added (regardless of casing).

For example with values of:

  • FunctionCase of lower_case
  • FunctionPrefix of pre_
  • FunctionSuffix of _post

Identifies and/or transforms function names like this:

Before:

char MY_Function_string();

After:

char pre_my_function_string_post();

  • GetConfigPerFile โ€” when true, the check looks for the configuration for wherever an identifier is actually declared. Super useful when your included header files are out here using a whole different style ๐Ÿฆ‹ Default value is true.

  • GlobalConstexprVariableCase โ€” set it and global constexpr variable names conform to the selected casing.
  • GlobalConstexprVariablePrefix โ€” global constexpr variable names get the prefix with the given value added (regardless of casing). Slay โœจ
  • GlobalConstexprVariableIgnoredRegexp โ€” global constexpr variable names matching this regex are left out of the identifier naming checks entirely.
  • GlobalConstexprVariableSuffix โ€” bestie, when this one's defined, the check makes sure global constexpr variable names get this suffix tacked on (regardless of casing, we don't discriminate ๐Ÿ’…).
  • GlobalConstexprVariableHungarianPrefix โ€” when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type. Yeah. That's the whole thing. ๐ŸŽ€

Okay so picture it with these values:

  • GlobalConstexprVariableCase of lower_case
  • GlobalConstexprVariablePrefix of pre_
  • GlobalConstexprVariableSuffix of _post
  • GlobalConstexprVariableHungarianPrefix of On

It identifies and/or transforms global constexpr variable names like this:

Before:

constexpr unsigned ImportantValue = 69;

After:

constexpr unsigned pre_important_value_post = 69;

  • GlobalConstantCase โ€” when defined, the check ensures global constant names conform to the casing you picked. slay โœจ
  • GlobalConstantPrefix โ€” when defined, the check will add this prefix to global constant names (regardless of casing).
  • GlobalConstantIgnoredRegexp โ€” identifier naming checks just won't be enforced for global constant names that match this regular expression. it's giving "you're on your own, bestie" ๐Ÿ™ˆ
  • GlobalConstantSuffix โ€” when defined, the check will add this suffix to global constant names (regardless of casing).
  • GlobalConstantHungarianPrefix โ€” when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.

Example time, using values of:

  • GlobalConstantCase of lower_case
  • GlobalConstantPrefix of pre_
  • GlobalConstantSuffix of _post
  • GlobalConstantHungarianPrefix of On

It identifies and/or transforms global constant names like so:

Before:

unsigned const MyConstGlobal_array[] = {1, 2, 3};

After:

unsigned const pre_myconstglobal_array_post[] = {1, 2, 3};

  • GlobalConstantPointerCase โ€” when defined, the check ensures global constant pointer names conform to the selected casing.
  • GlobalConstantPointerPrefix โ€” when defined, the check will add this prefix to global constant pointer names (regardless of casing).
  • GlobalConstantPointerIgnoredRegexp โ€” identifier naming checks won't be enforced for global constant pointer names matching this regular expression.
  • GlobalConstantPointerSuffix โ€” when defined, the check will add this suffix to global constant pointer names (regardless of casing).
  • GlobalConstantPointerHungarianPrefix โ€” when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.

For example using values of:

  • GlobalConstantPointerCase of lower_case
  • GlobalConstantPointerPrefix of pre_
  • GlobalConstantPointerSuffix of _post
  • GlobalConstantPointerHungarianPrefix of On

It identifies and/or transforms global constant pointer names like this:

Before:

int *const MyConstantGlobalPointer = nullptr;

After:

int *const pre_myconstantglobalpointer_post = nullptr;

  • GlobalFunctionCase โ€” when defined, the check ensures global function names conform to the selected casing.
  • GlobalFunctionPrefix โ€” when defined, the check will add this prefix to global function names (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 add this suffix to global function names (regardless of casing).

For example using values of:

  • GlobalFunctionCase of lower_case
  • GlobalFunctionPrefix of pre_
  • GlobalFunctionSuffix of _post

It identifies and/or transforms global function names like this:

Before:

void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);

After:

void pre_global_function_post(int PARAMETER_1, int const CONST_parameter);
lowkey iconic that the parameters get left completely alone here โ€” the check is only coming for the function name, not its little friends ๐Ÿซถ

  • GlobalPointerCase โ€” when defined, the check ensures global pointer names conform to the selected casing.
  • GlobalPointerPrefix โ€” when defined, the check will add this prefix to global pointer names (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 add this suffix to global pointer names (regardless of casing).
  • GlobalPointerHungarianPrefix โ€” when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.

For example using values of:

  • GlobalPointerCase of lower_case
  • GlobalPointerPrefix of pre_
  • GlobalPointerSuffix of _post
  • GlobalPointerHungarianPrefix of On

It identifies and/or transforms global pointer names like this:

Before:

int *GLOBAL3;

After:

int *pre_global3_post;

  • GlobalVariableCase โ€” when defined, the check ensures global variable names conform to the selected casing.
  • GlobalVariablePrefix โ€” when defined, the check will add this prefix to global variable names (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 add this suffix to global variable names (regardless of casing).
  • GlobalVariableHungarianPrefix โ€” when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.

For example using values of:

  • GlobalVariableCase of lower_case
  • GlobalVariablePrefix of pre_
  • GlobalVariableSuffix of _post
  • GlobalVariableHungarianPrefix of On

It identifies and/or transforms global variable names like this:

Before:

int GLOBAL3;

After:

int pre_global3_post;

  • IgnoreMainLikeFunctions โ€” when set to true, functions whose signature looks a whole lot like main or wmain won't get their parameter names checked. Default value is false. so yeah, main character energy gets a pass ๐Ÿ‘‘

  • InlineNamespaceCase โ€” when defined, the check ensures inline namespaces names conform to the selected casing.
  • InlineNamespacePrefix โ€” when defined, the check will add this prefix to inline namespaces names (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 add this suffix to inline namespaces names (regardless of casing).

For example using values of:

  • InlineNamespaceCase of lower_case
  • InlineNamespacePrefix of pre_
  • InlineNamespaceSuffix of _post

It identifies and/or transforms inline namespaces names like this:

Before:

namespace FOO_NS {
inline namespace InlineNamespace {
...
}
} // namespace FOO_NS

After:

namespace FOO_NS {
inline namespace pre_inlinenamespace_post {
...
}
} // namespace FOO_NS
notice the outer namespace FOO_NS is untouched โ€” this check is strictly about the inline one, bestie ๐Ÿ’–

  • LocalConstexprVariableCase โ€” when defined, the check ensures local constexpr variable names conform to the selected casing.
  • LocalConstexprVariablePrefix โ€” when defined, the check will add the prefix to local constexpr variable names (regardless of casing). ๐Ÿฆ‹
  • LocalConstexprVariableIgnoredRegexp โ€” identifier naming checks won't be enforced for local constexpr variable names matching this regular expression.
  • LocalConstexprVariableSuffix โ€” when defined, the check will add this suffix to local constexpr variable names (regardless of casing).
  • LocalConstexprVariableHungarianPrefix โ€” when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.

For example using values of:

  • LocalConstexprVariableCase of lower_case
  • LocalConstexprVariablePrefix of pre_
  • LocalConstexprVariableSuffix of _post
  • LocalConstexprVariableHungarianPrefix of On

It identifies and/or transforms local constexpr variable names like this:

Before:

void foo() { int const local_Constexpr = 420; }

After:

void foo() { int const pre_local_constexpr_post = 420; }

  • LocalConstantCase โ€” when defined, the check ensures local constant names conform to the selected casing.
  • LocalConstantPrefix โ€” when defined, the check will add this prefix to local constant names (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 add this suffix to local constant names (regardless of casing).
  • LocalConstantHungarianPrefix โ€” when enabled, the check ensures the declared identifier gets a Hungarian notation prefix based on the declared type.

For example using values of:

  • LocalConstantCase of lower_case
  • LocalConstantPrefix of pre_
  • LocalConstantSuffix of _post
  • LocalConstantHungarianPrefix of On

It identifies and/or transforms local constant names like this:

Before:

void foo() { int const local_Constant = 3; }

After:

void foo() { int const pre_local_constant_post = 3; }

  • LocalConstantPointerCase โ€” when defined, the check ensures local constant pointer names conform to the selected casing.
  • LocalConstantPointerPrefix โ€” when defined, the check will add this prefix to local constant pointer names (regardless of casing). ๐Ÿ“

๐Ÿ’… LocalConstantPointerIgnoredRegexp

okay so bestie โ€” identifier naming checks literally will not be enforced for local constant pointer names that match this regular expression. that's it. that's the whole vibe. a little safe-space regex, iykyk ๐Ÿซถ

๐ŸŽ€ LocalConstantPointerSuffix

when you define this, the check guarantees your local constant pointer names will add the suffix with the given value โ€” and yes, regardless of casing, no cap โœจ

๐Ÿ”ฅ LocalConstantPointerHungarianPrefix

when enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on the declared type. iconic behaviour honestly ๐Ÿ‘‘

so like, if you're running with these values:

  • LocalConstantPointerCase of lower_case
  • LocalConstantPointerPrefix of pre_
  • LocalConstantPointerSuffix of _post
  • LocalConstantPointerHungarianPrefix of On

then it identifies and/or transforms local constant pointer names like this:

Before:

void foo() { int const *local_Constant = 3; }

After:

void foo() { int const *pre_local_constant_post = 3; }

see how it ate that up?? ๐Ÿ’…


โญ LocalPointerCase

when defined, the check makes sure local pointer names conform to the selected casing. simple as that, fr fr.

๐ŸŒธ LocalPointerPrefix

when defined, the check makes sure local pointer names will add the prefix with the given value (regardless of casing).

๐Ÿฆ‹ LocalPointerIgnoredRegexp

identifier naming checks won't be enforced for local pointer names matching this regular expression. she's exempt, your honour ๐Ÿ™ˆ

๐Ÿ’– LocalPointerSuffix

when defined, the check makes sure local pointer names will add the suffix with the given value (regardless of casing).

๐ŸŒˆ LocalPointerHungarianPrefix

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:

  • LocalPointerCase of lower_case
  • LocalPointerPrefix of pre_
  • LocalPointerSuffix of _post
  • LocalPointerHungarianPrefix of On

it identifies and/or transforms local pointer names like so:

Before:

void foo() { int *local_Constant; }

After:

void foo() { int *pre_local_constant_post; }

glow-up complete โœจ


๐Ÿงš LocalVariableCase

when defined, the check makes sure local variable names conform to the selected casing.

๐Ÿ“ LocalVariablePrefix

when defined, the check makes sure local variable names will add the prefix 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:

  • LocalVariableCase of CamelCase
  • LocalVariableIgnoredRegexp of \w{1,2}

will exclude variables with a length less than or equal to 2 from the camel case check applied to other variables. tiny names get a pass, and honestly? deserved ๐Ÿฅบ

๐ŸŽ€ LocalVariableSuffix

when defined, the check makes sure local variable names will add the suffix with the given value (regardless of casing).

๐Ÿ‘‘ LocalVariableHungarianPrefix

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:

  • LocalVariableCase of lower_case
  • LocalVariablePrefix of pre_
  • LocalVariableSuffix of _post
  • LocalVariableHungarianPrefix of On

it identifies and/or transforms local variable names like this:

Before:

void foo() { int local_Constant; }

After:

void foo() { int pre_local_constant_post; }

periodt. ๐Ÿ’…


๐Ÿ’€ MacroDefinitionCase

when defined, the check makes sure macro definitions conform to the selected casing.

๐ŸŒŸ MacroDefinitionPrefix

when defined, the check makes sure macro definitions will add the prefix with the given value (regardless of casing).

๐Ÿซถ MacroDefinitionIgnoredRegexp

identifier naming checks won't be enforced for macro definitions matching this regular expression.

๐Ÿ”ฅ MacroDefinitionSuffix

when defined, the check makes sure macro definitions will add the suffix with the given value (regardless of casing).

for example using values of:

  • MacroDefinitionCase of lower_case
  • MacroDefinitionPrefix of pre_
  • MacroDefinitionSuffix of _post

it identifies and/or transforms macro definitions like this:

Before:

#define MY_MacroDefinition

After:

#define pre_my_macro_definition_post
โš ๏ธ Note: this will not warn on builtin macros or macros defined on the command line using the -D flag. those ones are simply not in her orbit, bestie ๐Ÿ’…

๐ŸŒธ MemberCase

when defined, the check makes sure member names conform to the selected casing.

๐ŸŽ€ MemberPrefix

when defined, the check makes sure member names will add the prefix 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 makes sure member names will add 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:

  • MemberCase of lower_case
  • MemberPrefix of pre_
  • MemberSuffix of _post
  • MemberHungarianPrefix of On

it identifies and/or transforms member names like this:

Before:

class Foo {
  char MY_ConstMember_string[4];
}

After:

class Foo {
  char pre_my_constmember_string_post[4];
}

she understood the assignment ๐Ÿ’…โœจ


๐ŸŒˆ MethodCase

when defined, the check makes sure method names conform to the selected casing.

๐Ÿงš MethodPrefix

when defined, the check makes sure method names will add the prefix with the given value (regardless of casing).

๐Ÿ’œ MethodIgnoredRegexp

identifier naming checks won't be enforced for method names matching this regular expression. exempt, mwah ๐Ÿ˜™

๐Ÿ“ MethodSuffix

when defined, the check makes sure method names will add the suffix with the given value (regardless of casing).

for example using values of:

  • MethodCase of lower_case
  • MethodPrefix of pre_
  • MethodSuffix of _post

it identifies and/or transforms method names like this:

Before:

class Foo {
  char MY_Method_string();
}

After:

class Foo {
  char pre_my_method_string_post();
}

lowkey obsessed with this transformation ngl ๐Ÿซถ


๐Ÿ‘‘ NamespaceCase

when defined, the check makes sure namespace names conform to the selected casing.

๐Ÿ”ฅ NamespacePrefix

when defined, the check makes sure namespace names will add the prefix 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 makes sure namespace names will add the suffix with the given value (regardless of casing).

for example using values of:

  • NamespaceCase of lower_case
  • NamespacePrefix of pre_
  • NamespaceSuffix of _post

it identifies and/or transforms namespace names like this:

Before:

namespace FOO_NS {
...
}

After:

namespace pre_foo_ns_post {
...
}

the ... stays exactly as it is, don't come for me ๐Ÿ˜ญ


๐ŸŽ€ ParameterCase

when defined, the check makes sure parameter names conform to the selected casing.

๐ŸŒธ ParameterPrefix

when defined, the check makes sure parameter names will add the prefix 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 makes sure parameter names will add 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:

  • ParameterCase of lower_case
  • ParameterPrefix of pre_
  • ParameterSuffix of _post
  • ParameterHungarianPrefix of On

it identifies and/or transforms parameter names like this:

Before:

void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);

After:

void GLOBAL_FUNCTION(int pre_parameter_post, int const CONST_parameter);

note how CONST_parameter said "not today" and stayed exactly where it was ๐Ÿ˜Œ main character energy fr


๐ŸŒˆ ParameterPackCase

when defined, the check makes sure parameter pack names conform to the selected casing.

๐Ÿงš ParameterPackPrefix

when defined, the check makes sure parameter pack names will add the prefix 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 makes sure parameter pack names will add the suffix with the given value (regardless of casing).

for example using values of:

  • ParameterPackCase of lower_case
  • ParameterPackPrefix of pre_
  • ParameterPackSuffix of _post

it identifies and/or transforms parameter pack names like this:

Before:

template <typename... TYPE_parameters> {
  void FUNCTION(int... TYPE_parameters);
}

After:

template <typename... TYPE_parameters> {
  void FUNCTION(int... pre_type_parameters_post);
}

the template parameter pack stayed untouched, the function parameter pack got the glow-up. we love a selective queen ๐Ÿ’…


๐Ÿ‘‘ PointerParameterCase

when defined, the check makes sure pointer parameter names conform to the selected casing.

๐Ÿ”ฅ PointerParameterPrefix

when defined, the check makes sure pointer parameter names will add the prefix 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 makes sure pointer parameter names will add 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:

  • PointerParameterCase of lower_case
  • PointerParameterPrefix of pre_
  • PointerParameterSuffix of _post
  • PointerParameterHungarianPrefix of On

it identifies and/or transforms pointer parameter names like this:

Before:

void FUNCTION(int *PARAMETER);

After:

void FUNCTION(int *pre_parameter_post);

(okay that's the tail end of that last snippet, we're moving on ๐Ÿ’…)


  • PrivateMemberCase โ€” bestie, when you define this one, the check makes sure private member names match whatever casing you picked. periodt.
  • PrivateMemberPrefix โ€” when defined, the check makes private member names slap on the prefix you give it (regardless of casing, she does not care about your casing rules here).
  • PrivateMemberIgnoredRegexp โ€” naming checks simply will not be enforced for private member names that match this regular expression. like, they're exempt, they have a hall pass ๐ŸŽŸ๏ธ
  • PrivateMemberSuffix โ€” when defined, the check makes private member names add the suffix with the given value (regardless of casing again, no cap).
  • PrivateMemberHungarianPrefix โ€” when enabled, the check ensures that the declared identifier will have a Hungarian notation prefix based on the declared type. yes it's a whole thing ๐Ÿ”ฅ

So for example, using values of:

  • PrivateMemberCase of lower_case
  • PrivateMemberPrefix of pre_
  • PrivateMemberSuffix of _post
  • PrivateMemberHungarianPrefix of On

It identifies and/or transforms private member names like this:

Before:

class Foo {
private:
  int Member_Variable;
}

After:

class Foo {
private:
  int pre_member_variable_post;
}

look at her glow up ๐Ÿ˜ญ๐Ÿ’–


  • PrivateMethodCase โ€” when defined, the check will ensure private method names conform to the selected casing.
  • PrivateMethodPrefix โ€” when defined, the check will ensure private method names will add the prefix 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 ensure private method names will add the suffix with the given value (regardless of casing).

For example using values of:

  • PrivateMethodCase of lower_case
  • PrivateMethodPrefix of pre_
  • PrivateMethodSuffix of _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();
}

she ate and left no crumbs ๐Ÿ“


  • ProtectedMemberCase โ€” when defined, the check will ensure protected member names conform to the selected casing.
  • ProtectedMemberPrefix โ€” when defined, the check will ensure protected member names will add the prefix with the given value (regardless of casing).
  • ProtectedMemberIgnoredRegexp โ€” identifier naming checks won't be enforced for protected member names matching this regular expression.
  • ProtectedMemberSuffix โ€” when defined, the check will ensure protected member names will add the suffix with the given value (regardless of casing).
  • ProtectedMemberHungarianPrefix โ€” 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:

  • ProtectedMemberCase of lower_case
  • ProtectedMemberPrefix of pre_
  • ProtectedMemberSuffix of _post
  • ProtectedMemberHungarianPrefix of On

Identifies and/or transforms protected member names as follows:

Before:

class Foo {
protected:
  int Member_Variable;
}

After:

class Foo {
protected:
  int pre_member_variable_post;
}

protected but still getting the full makeover treatment โœจ๐Ÿงš


  • ProtectedMethodCase โ€” when defined, the check will ensure protected method names conform to the selected casing.
  • ProtectedMethodPrefix โ€” when defined, the check will ensure protected method names will add the prefix with the given value (regardless of casing).
  • ProtectedMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for protected method names matching this regular expression.
  • ProtectedMethodSuffix โ€” when defined, the check will ensure protected method names will add the suffix with the given value (regardless of casing).

For example using values of:

  • ProtectedMethodCase of lower_case
  • ProtectedMethodPrefix of pre_
  • ProtectedMethodSuffix of _post

Identifies and/or transforms protected method names as follows:

Before:

class Foo {
protected:
  int Member_Method();
}

After:

class Foo {
protected:
  int pre_member_method_post();
}

  • PublicMemberCase โ€” when defined, the check will ensure public member names conform to the selected casing.
  • PublicMemberPrefix โ€” when defined, the check will ensure public member names will add the prefix with the given value (regardless of casing).
  • PublicMemberIgnoredRegexp โ€” identifier naming checks won't be enforced for public member names matching this regular expression.
  • PublicMemberSuffix โ€” when defined, the check will ensure public member names will add the suffix with the given value (regardless of casing).
  • PublicMemberHungarianPrefix โ€” 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:

  • PublicMemberCase of lower_case
  • PublicMemberPrefix of pre_
  • PublicMemberSuffix of _post
  • PublicMemberHungarianPrefix of On

Identifies and/or transforms public member names as follows:

Before:

class Foo {
public:
  int Member_Variable;
}

After:

class Foo {
public:
  int pre_member_variable_post;
}

public, loud, and fully renamed ๐Ÿ’…๐Ÿ”ฅ


  • PublicMethodCase โ€” when defined, the check will ensure public method names conform to the selected casing.
  • PublicMethodPrefix โ€” when defined, the check will ensure public method names will add the prefix with the given value (regardless of casing).
  • PublicMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for public method names matching this regular expression.
  • PublicMethodSuffix โ€” when defined, the check will ensure public method names will add the suffix with the given value (regardless of casing).

For example using values of:

  • PublicMethodCase of lower_case
  • PublicMethodPrefix of pre_
  • PublicMethodSuffix of _post

Identifies and/or transforms public method names as follows:

Before:

class Foo {
public:
  int Member_Method();
}

After:

class Foo {
public:
  int pre_member_method_post();
}

  • ScopedEnumConstantCase โ€” when defined, the check will ensure scoped enum constant names conform to the selected casing.
  • ScopedEnumConstantPrefix โ€” when defined, the check will ensure scoped enum constant names will add the prefix with the given value (regardless of casing).
  • ScopedEnumConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for scoped enum constant names matching this regular expression.
  • ScopedEnumConstantSuffix โ€” when defined, the check will ensure scoped enum constant names will add the suffix with the given value (regardless of casing).
  • ScopedEnumConstantHungarianPrefix โ€” 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:

  • ScopedEnumConstantCase of lower_case
  • ScopedEnumConstantPrefix of pre_
  • ScopedEnumConstantSuffix of _post
  • ScopedEnumConstantHungarianPrefix of On

Identifies and/or transforms enumeration constant names as follows:

Before:

enum class FOO { One, Two, Three };

After:

enum class FOO { pre_One_post, pre_Two_post, pre_Three_post };

all three of them got the same outfit, it's giving coordinated group costume ๐ŸŽ€๐Ÿ‘‘


  • StaticConstexprVariableCase โ€” when defined, the check will ensure static constexpr variable names conform to the selected casing.
  • StaticConstexprVariablePrefix โ€” when defined, the check will ensure static constexpr variable names will add the prefixed with the given value (regardless of casing).
  • StaticConstexprVariableIgnoredRegexp โ€” identifier naming checks won't be enforced for static constexpr variable names matching this regular expression.
  • StaticConstexprVariableSuffix โ€” when defined, the check will ensure static constexpr variable names will add the suffix with the given value (regardless of casing).
  • StaticConstexprVariableHungarianPrefix โ€” 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:

  • StaticConstexprVariableCase of lower_case
  • StaticConstexprVariablePrefix of pre_
  • StaticConstexprVariableSuffix of _post
  • StaticConstexprVariableHungarianPrefix of On

Identifies and/or transforms static constexpr variable names as follows:

Before:

static unsigned constexpr MyConstexprStatic_array[] = {1, 2, 3};

After:

static unsigned constexpr pre_my_constexpr_static_array_post[] = {1, 2, 3};

constexpr AND fashionable, we love a multitalented icon ๐Ÿ’œ


  • StaticConstantCase โ€” when defined, the check will ensure static constant names conform to the selected casing.
  • StaticConstantPrefix โ€” when defined, the check will ensure static constant names will add the prefix with the given value (regardless of casing).
  • StaticConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for static constant names matching this regular expression.
  • StaticConstantSuffix โ€” when defined, the check will ensure static constant names will add the suffix with the given value (regardless of casing).
  • StaticConstantHungarianPrefix โ€” 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:

  • StaticConstantCase of lower_case
  • StaticConstantPrefix of pre_
  • StaticConstantSuffix of _post
  • StaticConstantHungarianPrefix of On

Identifies and/or transforms static constant names as follows:

Before:

static unsigned const MyConstStatic_array[] = {1, 2, 3};

After:

static unsigned const pre_myconststatic_array_post[] = {1, 2, 3};

like yes the casing got squished together, that's literally what lower_case does to a name, don't come for her ๐Ÿ˜ญ๐Ÿ’€


  • StaticVariableCase โ€” when defined, the check will ensure static variable names conform to the selected casing.
  • StaticVariablePrefix โ€” set this and the check makes sure static variable names get your given value slapped on as a prefix (casing who? doesn't matter, the check does not care ๐Ÿ’…).
  • StaticVariableIgnoredRegexp โ€” any static variable name that matches this regular expression gets a hall pass; naming checks won't be enforced on it, periodt.
  • StaticVariableSuffix โ€” set this and static variable names get the given value tacked on as a suffix (again, regardless of casing ๐Ÿ™ˆ).
  • StaticVariableHungarianPrefix โ€” flip this on and the check makes the declared identifier carry a Hungarian notation prefix based on whatever type it was declared with. iconic behavior honestly.

So like, for example, using values of:

  • StaticVariableCase of lower_case
  • StaticVariablePrefix of pre_
  • StaticVariableSuffix of _post
  • StaticVariableHungarianPrefix of On

it identifies and/or transforms static variable names as follows:

Before:

static unsigned MyStatic_array[] = {1, 2, 3};

After:

static unsigned pre_mystatic_array_post[] = {1, 2, 3};

  • StructCase โ€” when defined, the check will ensure struct names conform to the selected casing โœจ.
  • StructPrefix โ€” when defined, the check will ensure struct names will add the prefix with the given value (regardless of casing).
  • StructIgnoredRegexp โ€” identifier naming checks won't be enforced for struct names matching this regular expression ๐Ÿซถ.
  • StructSuffix โ€” when defined, the check will ensure struct names will add the suffix with the given value (regardless of casing).

For example using values of:

  • StructCase of lower_case
  • StructPrefix of pre_
  • StructSuffix of _post

Identifies and/or transforms struct names as follows:

Before:

struct FOO {
  FOO();
  ~FOO();
};

After:

struct pre_foo_post {
  pre_foo_post();
  ~pre_foo_post();
};
ok but the constructors and destructor getting renamed too?? the check really said "we're doing the whole family" ๐Ÿ˜ญ and honestly? slay.

  • TemplateParameterCase โ€” when defined, the check will ensure template parameter names conform to the selected casing.
  • TemplateParameterPrefix โ€” when defined, the check will ensure template parameter names will add the prefix with the given value (regardless of casing).
  • TemplateParameterIgnoredRegexp โ€” identifier naming checks won't be enforced for template parameter names matching this regular expression.
  • TemplateParameterSuffix โ€” when defined, the check will ensure template parameter names will add the suffix with the given value (regardless of casing).

For example using values of:

  • TemplateParameterCase of lower_case
  • TemplateParameterPrefix of pre_
  • TemplateParameterSuffix of _post

Identifies and/or transforms template parameter names as follows:

Before:

template <typename T> class Foo {};

After:

template <typename pre_t_post> class Foo {};

  • TemplateTemplateParameterCase โ€” when defined, the check will ensure template template parameter names conform to the selected casing.
  • TemplateTemplateParameterPrefix โ€” when defined, the check will ensure template template parameter names will add the prefix with the given value (regardless of casing).
  • TemplateTemplateParameterIgnoredRegexp โ€” identifier naming checks won't be enforced for template template parameter names matching this regular expression.
  • TemplateTemplateParameterSuffix โ€” when defined, the check will ensure template template parameter names will add the suffix with the given value (regardless of casing).

For example using values of:

  • TemplateTemplateParameterCase of lower_case
  • TemplateTemplateParameterPrefix of pre_
  • TemplateTemplateParameterSuffix of _post

Identifies and/or transforms template template parameter names as follows:

Before:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... TYPE_parameters>

After:

template <template <typename> class pre_tpl_parameter_post, int COUNT_params,
          typename... TYPE_parameters>
yes bestie, "template template parameter" is a real thing and yes it's exactly as cursed as it sounds ๐Ÿ’€ but the check handles it, no cap.

  • TypeAliasCase โ€” when defined, the check will ensure type alias names conform to the selected casing.
  • TypeAliasPrefix โ€” when defined, the check will ensure type alias names will add the prefix with the given value (regardless of casing).
  • TypeAliasIgnoredRegexp โ€” identifier naming checks won't be enforced for type alias names matching this regular expression.
  • TypeAliasSuffix โ€” when defined, the check will ensure type alias names will add the suffix with the given value (regardless of casing).

For example using values of:

  • TypeAliasCase of lower_case
  • TypeAliasPrefix of pre_
  • TypeAliasSuffix of _post

Identifies and/or transforms type alias names as follows:

Before:

using MY_STRUCT_TYPE = my_structure;

After:

using pre_my_struct_type_post = my_structure;

  • TypedefCase โ€” when defined, the check will ensure typedef names conform to the selected casing.
  • TypedefPrefix โ€” when defined, the check will ensure typedef names will add the prefix with the given value (regardless of casing).
  • TypedefIgnoredRegexp โ€” identifier naming checks won't be enforced for typedef names matching this regular expression.
  • TypedefSuffix โ€” when defined, the check will ensure typedef names will add the suffix with the given value (regardless of casing).

For example using values of:

  • TypedefCase of lower_case
  • TypedefPrefix of pre_
  • TypedefSuffix of _post

Identifies and/or transforms typedef names as follows:

Before:

typedef int MYINT;

After:

typedef int pre_myint_post;

  • TypeTemplateParameterCase โ€” when defined, the check will ensure type template parameter names conform to the selected casing.
  • TypeTemplateParameterPrefix โ€” when defined, the check will ensure type template parameter names will add the prefix with the given value (regardless of casing).
  • TypeTemplateParameterIgnoredRegexp โ€” identifier naming checks won't be enforced for type template names matching this regular expression ๐Ÿ‘€ (yep, "type template names" โ€” that's what the page says, quoting it exactly).
  • TypeTemplateParameterSuffix โ€” 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:

  • TypeTemplateParameterCase of lower_case
  • TypeTemplateParameterPrefix of pre_
  • TypeTemplateParameterSuffix of _post

Identifies and/or transforms type template parameter names as follows:

Before:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... TYPE_parameters>

After:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... pre_type_parameters_post>
notice how only the type template parameter got touched here ๐ŸŽ€ the template template parameter and the value parameter stayed exactly as they were. main character energy = knowing which one is yours.

  • UnionCase โ€” when defined, the check will ensure union names conform to the selected casing.
  • UnionPrefix โ€” when defined, the check will ensure union names will add the prefix with the given value (regardless of casing).
  • UnionIgnoredRegexp โ€” identifier naming checks won't be enforced for union names matching this regular expression.
  • UnionSuffix โ€” when defined, the check will ensure union names will add the suffix with the given value (regardless of casing).

For example using values of:

  • UnionCase of lower_case
  • UnionPrefix of pre_
  • UnionSuffix of _post

Identifies and/or transforms union names as follows:

Before:

union FOO {
  int a;
  char b;
};

After:

union pre_foo_post {
  int a;
  char b;
};

  • ValueTemplateParameterCase โ€” when defined, the check will ensure value template parameter names conform to the selected casing.
  • ValueTemplateParameterPrefix โ€” when defined, the check will ensure value template parameter names will add the prefix with the given value (regardless of casing).
  • ValueTemplateParameterIgnoredRegexp โ€” identifier naming checks won't be enforced for value template parameter names matching this regular expression.
  • ValueTemplateParameterSuffix โ€” 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:

  • ValueTemplateParameterCase of lower_case
  • ValueTemplateParameterPrefix of pre_
  • ValueTemplateParameterSuffix of _post

Identifies and/or transforms value template parameter names as follows:

Before:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... TYPE_parameters>

After:

template <template <typename> class TPL_parameter, int pre_count_params_post,
          typename... TYPE_parameters>
COUNT_params โ†’ pre_count_params_postโ€ฆ so the case got lowercased and the affixes landed, together, in one glow-up ๐Ÿ’…โœจ

  • VariableCase โ€” when defined, the check will ensure variable names conform to the selected casing.
  • VariablePrefix โ€” when defined, the check will ensure variable names will add the prefix with the given value (regardless of casing).
  • VariableIgnoredRegexp โ€” identifier naming checks won't be enforced for variable names matching this regular expression.
  • VariableSuffix โ€” when defined, the check will ensure variable names will add the suffix with the given value (regardless of casing).
  • VariableHungarianPrefix โ€” 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:

  • VariableCase of lower_case
  • VariablePrefix of pre_
  • VariableSuffix of _post
  • VariableHungarianPrefix of On

Identifies and/or transforms variable names as follows:

Before:

unsigned MyVariable;

After:

unsigned pre_myvariable_post;

  • VirtualMethodCase โ€” when defined, the check will ensure virtual method names conform to the selected casing.
  • VirtualMethodPrefix โ€” when defined, the check will ensure virtual method names will add the prefix with the given value (regardless of casing).
  • VirtualMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for virtual method names matching this regular expression.
  • VirtualMethodSuffix โ€” 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:

  • VirtualMethodCase of lower_case
  • VirtualMethodPrefix of pre_
  • VirtualMethodSuffix of _post

Identifies and/or transforms virtual method names as follows:

Before:

class Foo {
public:
  virtual void MYVIRTUALMETHOD();
};

After:

class Foo {
public:
  virtual void pre_myvirtualmethod_post();
};
lowkey obsessed with how consistent this is โ€” every single identifier family gets the same Case / Prefix / IgnoredRegexp / Suffix quartet ๐ŸŽ€ learn the pattern once and you're set, iykyk.

๐Ÿ’… VirtualMethodSuffix ๐Ÿ’…

Okay so bestie, when this is defined, the check is literally gonna make sure that virtual method names add the suffix with the given value โ€” and that's regardless of casing, periodt. ๐Ÿซถ

For example, if you're using these values:

  • VirtualMethodCase of lower_case
  • VirtualMethodPrefix of pre_
  • VirtualMethodSuffix of _post

Then 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();
}

See how MemberFunction became pre_member_function_post? That's the vibe. โœจ


๐Ÿงš The default mapping table of Hungarian Notation ๐Ÿงš

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; this 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 the whole deal. ๐Ÿ’–

Okay so this next bit is the Hungarian Notation default mapping table โ€” it maps Decl โ†’ its prefix string, and it is a lot. The original lays it out as a big grid with three parallel columns set (primitive types / the C type spellings / the Microsoft type names), so I'm giving you the exact same pairs row by row instead of a grid, because terminals + grids = pain. Every single row is here, promise ๐Ÿซถ And bestie โ€” you can also have your own style in the config file, so this is just the default vibe โœจ

  • int8_t โ†’ i8 ยท signed int โ†’ si ยท BOOL โ†’ b
  • int16_t โ†’ i16 ยท signed short โ†’ ss ยท BOOLEAN โ†’ b
  • int32_t โ†’ i32 ยท signed short int โ†’ ssi ยท BYTE โ†’ by
  • int64_t โ†’ i64 ยท signed long long int โ†’ slli ยท CHAR โ†’ c
  • uint8_t โ†’ u8 ยท signed long long โ†’ sll ยท UCHAR โ†’ uc
  • uint16_t โ†’ u16 ยท signed long int โ†’ sli ยท SHORT โ†’ s
  • uint32_t โ†’ u32 ยท signed long โ†’ sl ยท USHORT โ†’ us
  • uint64_t โ†’ u64 ยท signed โ†’ s ยท WORD โ†’ w
  • char8_t โ†’ c8 ยท unsigned long long int โ†’ ulli ยท DWORD โ†’ dw
  • char16_t โ†’ c16 ยท unsigned long long โ†’ ull ยท DWORD32 โ†’ dw32
  • char32_t โ†’ c32 ยท unsigned long int โ†’ uli ยท DWORD64 โ†’ dw64
  • float โ†’ f ยท unsigned long โ†’ ul ยท LONG โ†’ l
  • double โ†’ d ยท unsigned short int โ†’ usi ยท ULONG โ†’ ul
  • char โ†’ c ยท unsigned short โ†’ us ยท ULONG32 โ†’ ul32
  • bool โ†’ b ยท unsigned int โ†’ ui ยท ULONG64 โ†’ ul64
  • _Bool โ†’ b ยท unsigned char โ†’ uc ยท ULONGLONG โ†’ ull
  • int โ†’ i ยท unsigned โ†’ u ยท HANDLE โ†’ h
  • size_t โ†’ n ยท long long int โ†’ lli ยท INT โ†’ i
  • short โ†’ s ยท long double โ†’ ld ยท INT8 โ†’ i8
  • signed โ†’ i ยท long long โ†’ ll ยท INT16 โ†’ i16
  • unsigned โ†’ u ยท long int โ†’ li ยท INT32 โ†’ i32
  • long โ†’ l ยท long โ†’ l ยท INT64 โ†’ i64
  • long long โ†’ ll ยท ptrdiff_t โ†’ p ยท UINT โ†’ ui
  • unsigned long โ†’ ul ยท void โ†’ none ยท UINT8 โ†’ u8
  • long double โ†’ ld ยท UINT16 โ†’ u16
  • ptrdiff_t โ†’ p ยท UINT32 โ†’ u32
  • wchar_t โ†’ wc ยท UINT64 โ†’ u64
  • short int โ†’ si ยท PVOID โ†’ p
  • short โ†’ s
๐Ÿ’€ yes, long โ†’ l really does show up twice in the source table, and no, I'm not "fixing" it โ€” I just report the tea, I don't spill it differently.

There are, like, a bunch more trivial options for Hungarian Notation โ€” here's the fam:

  • HungarianNotation.General.* โ€” these ones don't belong to any specific Decl, they're just the general-vibes options ๐ŸŽ€
  • HungarianNotation.CString.* โ€” options for NULL-terminated strings ๐Ÿงต
  • HungarianNotation.DerivedType.* โ€” options for derived types ๐ŸŒ€
  • HungarianNotation.PrimitiveType.* โ€” options for primitive types ๐Ÿงฑ
  • HungarianNotation.UserDefinedType.* โ€” options for user-defined types ๐Ÿ‘‘
  • HungarianNotation.General.TreatStructAsClass โ€” this one's the main character of the General group, no cap โœจ

๐ŸŽ€ Options for Hungarian Notation ๐ŸŽ€

  • HungarianNotation.General.TreatStructAsClass
  • 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

okay so when this one's defined, the check treats the naming of a struct like it's a class ๐Ÿ’… the default value is false.

HungarianNotation.DerivedType.Array

when defined, the check makes sure the variable name gets that given string as a prefix added on. default prefix is a. slay โœจ

HungarianNotation.DerivedType.Pointer

when defined, the check makes sure the variable name gets that given string as a prefix added on. default prefix is p.

HungarianNotation.DerivedType.FunctionPointer

when defined, the check makes sure the variable name gets that given string as a prefix added on. default prefix is fn.

Before:

// Array
int DataArray[2] = {0};

// Pointer
void *DataBuffer = NULL;

// FunctionPointer
typedef void (*FUNC_PTR)();
FUNC_PTR FuncPtr = NULL;

After:

// Array
int aDataArray[2] = {0};

// Pointer
void *pDataBuffer = NULL;

// FunctionPointer
typedef void (*FUNC_PTR)();
FUNC_PTR fnFuncPtr = NULL;

HungarianNotation.CString.CharPointer

when defined, the check makes sure the variable name gets that given string as a prefix added on. default prefix is sz ๐ŸŽ€

HungarianNotation.CString.CharArray

when defined, the check makes sure the variable name gets that given string as a prefix added on. default prefix is sz.

HungarianNotation.CString.WideCharPointer

when defined, the check makes sure the variable name gets that given string as a prefix added on. default prefix is wsz.

HungarianNotation.CString.WideCharArray

when defined, the check makes sure the variable name gets that given string as a prefix added on. default prefix is wsz.

Before:

// CharPointer
const char *NamePtr = "Name";

// CharArray
const char NameArray[] = "Name";

// WideCharPointer
const wchar_t *WideNamePtr = L"Name";

// WideCharArray
const wchar_t WideNameArray[] = L"Name";

After:

// CharPointer
const char *szNamePtr = "Name";

// CharArray
const char szNameArray[] = "Name";

// WideCharPointer
const wchar_t *wszWideNamePtr = L"Name";

// WideCharArray
const wchar_t wszWideNameArray[] = L"Name";

HungarianNotation.PrimitiveType.*

when defined, the check makes sure the variable name of the primitive types involved gets that given string as a prefix added on. the default prefixes are defined in the default mapping table ๐Ÿฆ‹

HungarianNotation.UserDefinedType.*

when defined, the check makes sure the variable name of the primitive types involved gets that given string as a prefix added on. the default prefixes are defined in the default mapping table. (yes bestie, the wording is the same as above โ€” that's how it is, no cap ๐Ÿ™ˆ)

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

heads up bestie โ€” this check got renamed to readability-implicit-bool-conversion. that's the whole entry. it's giving "moved, leave a forwarding address" ๐Ÿ’Œ


## readability-implicit-bool-conversion

okay so this check is the one that hunts down implicit conversions between built-in types and booleans ๐Ÿ”ฅ depending on your use case it might just be a lil readability glow-up, or it might legitimately point at potential bugs that were sitting there unnoticed this whole time because implicit conversions are sneaky like that ๐Ÿ˜ญ

here's a real-world example of a bug that was 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 aftermath of a refactor that did NOT go to plan โ€” the type of m_foo got changed from bool to int, and the programmer forgot to update all the occurrences of bool. so the leftover code is no longer correct, yet it still compiles with zero visible warnings ๐Ÿ’€ the audacity.

on top of issuing warnings, it also hands you fix-it hints to help solve the reported issues. that's useful for improving readability of code, like this:

void conversionsToBool() {
  float floating;
  bool boolean = floating;
  // ^ propose replacement: bool boolean = floating != 0.0f;

  int integer;
  if (integer) {}
  // ^ propose replacement: if (integer != 0) {}

  int* pointer;
  if (!pointer) {}
  // ^ propose replacement: if (pointer == nullptr) {}

  while (1) {}
  // ^ propose replacement: while (true) {}
}

void functionTakingInt(int param);

void conversionsFromBool() {
  bool boolean;
  functionTakingInt(boolean);
  // ^ propose replacement: functionTakingInt(static_cast<int>(boolean));

  functionTakingInt(true);
  // ^ propose replacement: functionTakingInt(1);
}

in general, these are the conversion types it checks ๐Ÿ‘‘

  • integer expression/literal to boolean (conversion from a single bit bitfield to boolean is explicitly allowed, since there's no ambiguity / information loss in this case),
  • floating expression/literal to boolean,
  • pointer/pointer to member/nullptr/NULL to boolean,
  • boolean expression/literal to integer (conversion from boolean to a single bit bitfield is explicitly allowed),
  • boolean expression/literal to floating.

the rules for generating those fix-it hints are:

  • in case of conversions from other built-in type to bool, an explicit comparison is proposed so it's crystal clear what exactly is being compared:
    • bool boolean = floating; is changed to bool 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 gets simplified:
    • if (!pointer) is changed to if (pointer == nullptr),
  • in case of conversions from bool to other built-in types, an explicit static_cast (or a C-style cast since C23) is proposed to make it clear that a conversion is taking place:
    • int integer = boolean; is changed to int 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 to functionTakingBool(false);,
    • functionTakingInt(true); is changed to functionTakingInt(1);,
    • for other types, appropriate literals are used (false, true, 0, 1, 0u, 1u, 0.0f, 1.0f, 0.0, 1.0f).

some extra accommodations are made for pre-C++11 dialects:

  • false literal conversion to pointer is detected,
  • instead of nullptr literal, 0 is proposed as replacement.

and some extra accommodations are made for C:

  • bool (or _Bool) operands in logical operators (&&, ||) are ignored.
  • bool (or _Bool) conditions in conditional operators (?:) are ignored.
โš ๏ธ occurrences of implicit conversions inside macros and template instantiations are deliberately ignored, because it's not clear how to deal with such cases. not me being honest about my limits ๐Ÿ’…

Options

  • AllowIntegerConditions โ€” when true, the check will allow conditional integer conversions. default is false.
  • AllowPointerConditions โ€” when true, the check will allow conditional pointer conversions. default is false.
  • AllowLogicalOperatorConversion โ€” when true, the check will suppress warnings for implicit conversions of logical operator results (&&, ||, !) to bool. these operators always produce values equal to 0 or 1, so the conversion is safe. default is false.
  • UseUpperCaseLiteralSuffix โ€” when true, the replacements will use an uppercase literal suffix in the provided fixes. default is false.

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 ๐Ÿ‘€ like, so simple but so iconic.

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 is here to help enforce consistency in large projects, where it very often happens that a function's definition gets refactored and the parameter names change โ€” but its declaration in the header file never gets updated ๐Ÿ’€ with this check we can easily find and correct those inconsistencies, keeping declaration and definition always in sync. main character behaviour, honestly ๐Ÿซถ

unnamed parameters are allowed, and they're 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 on the assumption that the function definition is the most up-to-date version, since it directly references parameter names in its body. example:

void foo(int a); // warning and fix-it hint (replace "a" to "b")
int foo(int b) { return b + 2; } // definition with use of "b"

In the case of multiple redeclarations or function template specializations, a warning is issued for every single redeclaration or specialization that's inconsistent with the definition or with the first declaration seen in a translation unit. like, it's keeping receipts on all of them ๐Ÿ’…

โœจ Options โœจ

  • IgnoreMacros โ€” set this to true (and bestie, the default is true) and the check will simply not warn about names declared inside macros. it's giving "not my circus" ๐ŸŽช
  • Strict โ€” set this to true (default is false) and names must match exactly... or be entirely absent. no vibes-based matching allowed ๐Ÿ”ฅ

๐Ÿฆ‹ readability-inconsistent-ifelse-braces ๐Ÿฆ‹

Catches if/else statements where one branch is wearing braces and the other one forgot to get dressed. it's the mismatched-socks of C++ ๐Ÿ’€

Before:

if (condition) {
  statement;
} else
  statement;

if (condition)
  statement;

if (condition)
  statement;
else
  statement;

After:

if (condition) {
  statement;
} else {
  statement;
}

if (condition)
  statement;

if (condition)
  statement;
else
  statement;

๐ŸŒธ readability-isolate-declaration ๐ŸŒธ

Spots local variable declarations that are out here declaring more than one variable at once, and tries to refactor the code so it's one statement per declaration. one variable, one statement, one moment of main character energy โœจ

The automatic code-transformation will use the same indentation as the original for every created statement, and it adds a line break after each statement. It keeps the order of the variable declarations consistent too, because chaos is not the vibe ๐ŸŽ€

void f() {
  int * pointer = nullptr, value = 42, * const const_ptr = &value;
  // This declaration will be diagnosed and transformed into:
  // int * pointer = nullptr;
  // int value = 42;
  // int * const const_ptr = &value;
}

The check excludes places where it's necessary or common to declare multiple variables in one statement and there's no other way supported in the language. also heads up: structured bindings are not considered, so don't come for her about those ๐Ÿ™ˆ

// 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. not invited to this party, sorry ๐Ÿซถ

The check currently does not support the automatic transformation of member-pointer-types. she'll clock it, but she won't touch it:

struct S {
  int a;
  const int b;
  void f() {}
};

void f() {
  // Only a diagnostic message is emitted
  int S::*p = &S::a, S::*const q = &S::a;
}

Furthermore, the transformation is very cautious when it detects various kinds of macros or preprocessor directives in the range of the statement. In that case the transformation will not happen, so we avoid unexpected side-effects due to macros. lowkey paranoid, but honestly? valid ๐Ÿ’…

#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. like, where did 3.1415926535 come from? who invited it? ๐Ÿ˜ญ

Many coding guidelines advise replacing the magic values with symbolic constants to improve readability. here are a few references, bestie, for the receipts:

  • Rule ES.45: Avoid "magic constants"; use symbolic constants in C++ Core Guidelines
  • 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 (slay):

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. math! she did it ๐Ÿงš

As a special case for integral values, all powers of two can be accepted without warning by enabling the IgnorePowersOf2IntegerValues option. cute.

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. yeah this is the unsexy part but it fr fr matters ๐Ÿ’€

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). two for one, we love to see it ๐Ÿ’–

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. no thoughts, no floats โœจ

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 behavior, honestly.

โœจ Options โœจ

  • IgnoredIntegerValues โ€” semicolon-separated list of magic positive integers that will be accepted without a warning. Default values are {1, 2, 3, 4}, and 0 is accepted unconditionally. no notes ๐Ÿ‘‘
  • IgnorePowersOf2IntegerValues โ€” boolean value indicating whether to accept all powers-of-two integer values without warning. Default value is false.
  • IgnoredFloatingPointValues โ€” semicolon-separated list of magic positive floating point values that will be accepted without a warning. Default values are {1.0, 100.0} and 0.0 is accepted unconditionally.
  • IgnoreAllFloatingPointValues โ€” boolean value indicating whether to accept all floating point values without warning. Default value is false.
  • 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 is true.
  • IgnoreTypeAliases โ€” boolean value indicating whether to accept magic numbers in typedef or using declarations. Default value is false.
  • IgnoreUserDefinedLiterals โ€” boolean value indicating whether to accept magic numbers in user-defined literals. Default value is false.

๐Ÿ’œ readability-make-member-function-const ๐Ÿ’œ

Finds non-static member functions that could be made const, because the functions don't use this in a non-const way. it's giving "you literally never touch anything, you can be const, bestie" ๐Ÿ’…

This check tries to annotate methods according to logical constness (not physical constness). Therefore, it will suggest adding a const qualifier to a non-const method only if this method does something that is already possible through the public interface on a const pointer to the object:

  • reading a public member variable
  • calling a public const-qualified member function
  • returning const-qualified this
  • passing const-qualified this as a parameter.

This check will also suggest adding 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 adding a const to a non-const method if the method reads a private member variable of pointer type, because that allows modifying the pointee, which might not preserve logical constness. For the same reason, it does not allow calling private member functions or member functions on private member variables. like, that's a whole loophole and she's not falling for it ๐Ÿ™ˆ

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 this at all (see readability-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());
            }
            ...
          };

okay so real talk: after you apply the fixes this check suggests and then run it again, it might legit find even more member functions you can mark const. It's giving onion energy. Layers. ๐Ÿง…โœจ


readability-math-missing-parentheses ๐Ÿงฎ

This one clocks missing parentheses in math expressions that involve operators of different priorities. Bestie, operator precedence is real and it does not care about your feelings.

Parentheses in math expressions clarify the order of operations โ€” especially when you've got different-priority operators vibing in the same line. Long or multiline expressions can hide that order and boom ๐Ÿ’€ coding error. IDEs can help by highlighting parens, sure, but explicitly writing them also shows what the dev actually meant when they wrote the thing. Having them there reduces ambiguity and errors, and honestly it makes the code way more readable and maintainable. Slay.

Before:

int x = 1 + 2 * 3 - 4 / 5;

After:

int x = 1 + (2 * 3) - (4 / 5);

readability-misleading-indentation ๐Ÿซง

Correct indentation helps you actually understand code. When the syntactic structure and the indentation don't match up, serious problems can be hiding in there ๐Ÿ™ˆ. Missing braces also make code significantly harder to read, so like โ€” use braces, it's free.

The way to dodge a dangling else? Always double-check that an else belongs to the if that starts in the same column. That's the whole trick, iykyk.

You can omit braces when the inner part of, say, an if statement only has one statement in it. But in that case you should begin the next statement in the same column as the if. Otherwise it's a trap.

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 together. Mix them and the vibe check fails, no cap.


readability-misplaced-array-index ๐ŸŒ€

This check warns you about unusual array index syntax. And by unusual I mean chaotic.

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;
}

So why does the check even care? It warns about this unusual syntax for readability reasons:

  • There are programmers who are simply not familiar with this unusual syntax. ๐Ÿ˜ญ
  • There's a real chance variables got mixed up.

Like, Y[X] does technically work โ€” but bestie, at what cost. ๐Ÿ’…


readability-named-parameter ๐Ÿท๏ธ

Finds functions with unnamed arguments. Yes, that's it. That's the check.

It implements the following rule originating in the Google C++ Style Guide:

https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions

The rule: all parameters should have the same name in both the function declaration and definition. If a parameter isn't used, its name can be commented out in a function definition.

int doingSomething(int a, int b, int c);

int doingSomething(int a, int b, int /*c*/) {
    // Ok: the third param is not used
    return a + b;
}

Corresponding cpplint.py check name: readability/function.

Options

  • InsertPlainNamesInForwardDecls โ€” if set to true, the check will insert parameter names without comments for forward declarations only. Otherwise, it'll insert the parameter names as comments (like /*param*/). Default is false.

readability-non-const-parameter ๐Ÿ”’

This check finds function parameters of a pointer type that could be changed to point to a constant type instead. Glow-up for your function signature.

When const is used properly, so many mistakes just โœจ vanish โœจ. Advantages of using const properly:

  • it prevents unintentional modification of data;
  • you get additional warnings, like using uninitialized data;
  • it makes it easier for developers to see possible side effects.

Important caveat though: this check is not strict about constness โ€” it only warns when the constness will make the function interface safer. It's not out here being extra for no reason.

// 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 a consistent token representation for invoked binary, unary and overloaded operators in C++ code. It supports both the traditional and the alternative representations of operators โ€” like && and and, || and or, and so on. Both are valid, bestie, but pick a lane.

In C++ land you genuinely have the option to choose between two distinct representations: traditional token representation and alternative token representation. Traditional tokens use the symbols โ€” &&, ||, ! โ€” while alternative tokens use the more descriptive words: and, or, not.

Here's the full mapping of traditional โ†” alternative tokens:

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 config doesn't enforce either one. You need to configure it explicitly. No defaults jumping out at you here. ๐Ÿ™…

To configure the check to enforce Traditional Token Representation for all operators, set options to &&;&=;&;|;~;!;!=;||;|=;^;^=.

To configure the check to enforce Alternative Token Representation for all operators, set options to and;and_eq;bitand;bitor;compl;not;not_eq;or;or_eq;xor;xor_eq.

And here's the fun part: you do not need to enforce all operators. You can mix representations however you want by specifying a semicolon-separated list of both traditional and alternative tokens in the config โ€” like and;||;not. Chef's kiss. ๐Ÿ‘‘

  • BinaryOperators โ€” this option lets you specify a semicolon-separated list of binary operators for which you want to enforce a specific token representation. The default value is an empty string.
  • OverloadedOperators โ€” this option lets you specify a semicolon-separated list of overloaded operators for which you want to enforce a specific token representation. The default value is an empty string.

readability-qualified-auto ๐Ÿช„

Adds pointer qualifications to auto-typed variables that get deduced to pointers. Basically it makes your auto say what it actually is, and honestly? Respect.

The LLVM Coding Standards advise making it obvious when an auto-typed variable is a pointer. So this check transforms auto into 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. And pointers to pointers will not be fully qualified. Don't @ me, that's just how it is.

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 to true, the check will add const qualifiers to variables defined as auto * or auto & when applicable. Default value is true. ๐Ÿ’–
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 gets transformed into this ๐Ÿ’…:

const auto *Foo1 = cast<const int *>(Bar1);
auto *Foo2 = cast<const int *>(Bar2);
auto &Foo3 = cast<const int &>(Bar3);

Otherwise? Bestie, it becomes this instead โœจ:

const auto *Foo1 = cast<const int *>(Bar1);
const auto *Foo2 = cast<const int *>(Bar2);
const auto &Foo3 = cast<const int &>(Bar3);
Heads up ๐Ÿซถ โ€” in the LLVM alias, the default value is false. Not a typo, that's literally what it says.
  • AllowedTypes โ€” a semicolon-separated list of names of types to ignore when auto is deduced to that type or a pointer to that type ๐ŸŽ€. Lowkey important: this distinguishes type aliases from the original type, so specifying e.g. my_int will not suppress reports about int even if it's defined as a typedef alias for int. Regular expressions are accepted, e.g. [Rr]ef(erence)?$ matches every type with suffix Ref, ref, Reference and reference ๐ŸŒˆ. If a name in the list contains the sequence :: it is matched against the qualified type name (i.e. namespace::Type), otherwise it is matched against only the type name (i.e. Type). E.g. to suppress reports for std::array iterators use std::array<.*>::(const_)?iterator. The default is an empty string ๐Ÿ’œ.
  • IgnoreAliasing โ€” if set to true the check will use the underlying type to determine the type that auto is deduced to. If set to false the check will not look beyond the first type alias. Default value is true ๐Ÿฆ‹.
using IntPtr = int*;
IntPtr foo();

auto bar = foo();

If IgnoreAliasing is set to true, it will be transformed into:

auto *bar = foo();

Otherwise no changes will occur ๐Ÿ™ˆ.

๐Ÿ’€ Limitations

When IgnoreAliasing is set to false, there are cases where Clang has not preserved the type alias and the underlying type will be used, so false positives may occur ๐Ÿ˜ญ. Yeah. It's giving "not the check's fault."

For example:

using IntPtr = int *;

void loopPtr(const std::vector<IntPtr> &VectorIntPtr) {

  // May fail for IgnoreAliasing==false as AST does not have the 'IntPtr'
  for (auto Data : VectorIntPtr) {
  }
}

๐ŸŒธ readability-redundant-access-specifiers

Finds classes, structs, and unions containing redundant member (field and method) access specifiers ๐Ÿ‘‘. Basically the public: you didn't need.

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 โœจ. Iconic. Ate. No notes.

Options

  • CheckFirstDeclaration โ€” if set to true, the check will also diagnose if the first access specifier declaration is redundant (e.g. private inside class, or public inside struct or union) ๐ŸŽ€. Default is false.

Example

struct Bar {
public:
  int x;
}

If CheckFirstDeclaration option is enabled, a warning about redundant access specifier will be emitted, because public is the default member access for structs ๐Ÿ’….


๐Ÿ”ฅ readability-redundant-casting

Detects explicit type casting operations that involve the same source and destination types, and subsequently recommends their removal ๐Ÿงš. It covers a whole range of explicit casting operations, including static_cast, const_cast, C-style casts, and reinterpret_cast. Its primary objective is to enhance code readability and maintainability by eliminating unnecessary type casting โ€” and honestly? Slay.

int value = 42;
int result = static_cast<int>(value);

In this example, the static_cast<int>(value) is redundant, as it performs a cast from an int to another int ๐Ÿ’€.

Casting operations involving constructor conversions, user-defined conversions, functional casts, type-dependent casts, casts between distinct type aliases that refer to the same underlying type, as well as bitfield-related casts and casts directly from lvalue to rvalue, are all disregarded by the check ๐Ÿ™ˆ. She's picky. Respect it.

Options

  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true ๐Ÿซถ.
  • IgnoreTypeAliases โ€” when set to false, the check will consider type aliases, and when set to true, it will resolve all type aliases and operate on the underlying types. Default is false ๐ŸŒธ.
  • IgnoreImplicitCasts โ€” when set to false, the check will flag casts as redundant when at least one operand in an expression is implicitly cast to match the result type of the explicit cast. When set to true the casts will not be flagged. Default is false โœจ.

For example, with IgnoreImplicitCasts = false:

static_cast<float>(2.0f + 1);  // redundant (1 implicitly converts to float)

๐Ÿฆ‹ readability-redundant-control-flow

This check looks for procedures (functions returning no value) with return statements at the end of the function. Such return statements are redundant ๐Ÿ‘‘.

Loop statements (for, while, do while) are checked for redundant continue statements at the end of the loop body, too.

Examples:

The following function f contains a redundant return statement:

extern void g();
void f() {
  g();
  return;
}

becomes

extern void g();
void f() {
  g();
}

The following function k contains a redundant continue statement:

void k() {
  for (int i = 0; i < 10; ++i) {
    continue;
  }
}

becomes

void k() {
  for (int i = 0; i < 10; ++i) {
  }
}

๐Ÿ’œ readability-redundant-declaration

Finds redundant variable and function declarations ๐ŸŽ€.

extern int X;
extern int X;

becomes

extern int X;

Such redundant declarations can be removed without changing program behavior ๐ŸŒˆ. They can for instance be unintentional left overs from previous refactorings when code has been moved around. Having redundant declarations could in worst case mean that there are typos in the code that cause bugs ๐Ÿ˜ญ. So like, clean up your room bestie.

Normally the code can be automatically fixed โ€” clang-tidy can remove the second declaration. However, there are 2 cases when you need to fix the code manually ๐Ÿ™ˆ:

  • When the declarations are in different header files;
  • When multiple variables are declared together.

Options

  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true โœจ.

โญ readability-redundant-function-ptr-dereference

Finds redundant dereferences of a function pointer ๐Ÿ’…. Like, why are you dereferencing twice. Be so serious.

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);

๐ŸŽ€ readability-redundant-inline-specifier

Detects redundant inline specifiers on function and variable declarations ๐ŸŒธ.

Examples:

constexpr inline void f() {}

In the example above the keyword inline is redundant since constexpr functions are implicitly inlined โœจ.

class MyClass {
    inline void myMethod() {}
};

In the example above the keyword inline is redundant since member functions defined entirely inside a class/struct/union definition are implicitly inlined ๐Ÿ‘‘.

Options

  • StrictMode โ€” if set to true, the check will also flag functions and variables that already have internal linkage as redundant. Default is false ๐Ÿซถ.

๐Ÿงš readability-redundant-lambda-parameter-list

Finds lambda expressions with a redundant empty parameter list and removes it ๐Ÿ’€. Yes, the () that does nothing. She's coming for it.

In C++11 and later, a lambda with no parameters does not require an explicit () unless it has a specifier such as mutable, noexcept, or a trailing return type. In C++23 and later, () is redundant even when such specifiers are present ๐Ÿ”ฅ.

// C++11 and later - the following lambdas will be rewritten:
auto a = []() { return 42; };
// becomes:
auto a = [] { return 42; };

auto b = [x = 1]() { return x; };
// becomes:
auto b = [x = 1] { return x; };

// C++23 and later - the following lambdas will also be rewritten:
auto c = []() mutable {};
// becomes:
auto c = [] mutable {};

auto d = []() noexcept {};
// becomes:
auto d = [] noexcept {};

auto e = []() -> int { return 0; };
// becomes:
auto e = [] -> int { return 0; };

๐ŸŒˆ readability-redundant-member-init

Finds member initializations that are unnecessary because the same default constructor would be called if they were not present ๐Ÿ’….

Example

// Explicitly initializing the member s and v is unnecessary.
class Foo {
public:
  Foo() : s() {}

private:
  std::string s;
  std::vector<int> v {};
};

Options

  • IgnoreMacros โ€” when true, the check will ignore member initializations where the initializer involves a macro expansion. Default is false ๐ŸŽ€.
  • IgnoreBaseInCopyConstructors โ€” default is false ๐ŸŒธ.

When true, the check will ignore unnecessary base class initializations within copy constructors, since some compilers issue warnings/errors when base classes are not explicitly initialized in copy constructors ๐Ÿ˜ญ. For example, gcc with -Wextra or -Werror=extra issues warning or error base class 'Bar' should be explicitly initialized in the copy constructor if Bar() were removed in the following example:

// Explicitly initializing member s and base class Bar is unnecessary.
struct Foo : public Bar {
  // Remove s() below. If IgnoreBaseInCopyConstructors!=0, keep Bar().
  Foo(const Foo& foo) : Bar(), s() {}
  std::string s;
};

๐Ÿฆ‹ readability-redundant-nested-if

Finds nested if statements that can be merged by combining their conditions with && ๐Ÿ‘‘. Two ifs? One if. Periodt.

Example:

if (a) {
  if (b) {
    work();
  }
}

becomes

if ((a) && (b)) {
  work();
}

The check also supports outer declaration conditions in C++17 and later ๐Ÿ”ฅ:

if (bool X = ready()) {
  if (can_run()) {
    work();
  }
}

becomes

if (bool X = ready(); X && (can_run())) {
  work();
}

For if constexpr, dependent nested conditions are merged only when they can be formed outside the discarded branch ๐ŸŒธ. This includes conditions such as non-type template parameters and requires expressions, but excludes conditions such as sizeof(typename T::type) after an earlier dependent condition ๐Ÿ™ˆ.

  • AllowUserDefinedBoolConversion โ€” bestie, when this is set to true, the check also diagnoses chains whose merged conditions require a user-defined conversion to bool. The fix-its insert static_cast<bool>(...) wherever needed so the merged condition still uses built-in && semantics. Default is false. โœจ

๐Ÿฆ‹ readability-redundant-parentheses ๐Ÿฆ‹

This one detects redundant parentheses. ๐Ÿ’… When youโ€™re modifying code, it is so easy to forget to remove the matching parentheses, and then the code gets overly lengthy. When the expression is complex, finding the matching parentheses becomes particularly difficult.

Example

(1);
((a + 2)) * 3;
(a);
("aaa");

Currently this check does not take into account the precedence of operations. Even if the expression within the parentheses has a higher priority than that outside the parentheses โ€” in other words, removing the parentheses will not affect the semantics โ€” it still doesnโ€™t warn here:

int a = (1 * 2) + 3; // no warning

Options

  • AllowedDecls โ€” Semicolon-separated list of regular expressions matching names of declarations to ignore when the parentheses are around. Declarations can include variables or functions. The default is an std::max;std::min.

Some STL library functions may have the same name as a widely used function-like macro. For example, std::max and the max macro. A workaround to distinguish them is adding parentheses around functions to prevent the function-like macro. ๐Ÿซถ


๐ŸŒธ readability-redundant-preprocessor ๐ŸŒธ

Finds potentially redundant preprocessor directives. At the moment, the following cases are detected:

  • #ifdef .. #endif pairs 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 .. #endif pairs. For example:
#ifndef FOO
#ifndef FOO // inner ifndef is considered redundant
void f();
#endif
#endif
  • #ifndef inside an #ifdef with the same condition:
#ifdef FOO
#ifndef FOO // inner ifndef is considered redundant
void f();
#endif
#endif
  • #ifdef inside an #ifndef with the same condition:
#ifndef FOO
#ifdef FOO // inner ifdef is considered redundant
void f();
#endif
#endif
  • #if .. #endif pairs 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

๐Ÿ’œ readability-redundant-qualified-alias ๐Ÿ’œ

Finds redundant identity type aliases that re-expose a qualified name and can be replaced with a using declaration.

using seconds = std::chrono::seconds;

// becomes

using std::chrono::seconds;

The check is conservative and only warns when the alias name exactly matches the unqualified name of a non-dependent, non-specialized named type written with a qualifier. It skips alias templates, dependent forms, elaborated keywords (class, struct, enum, typename), and cases involving macros.

Options

  • OnlyNamespaceScope โ€” When true, only consider aliases declared in a namespace or the translation unit. When false, also consider aliases declared inside classes, functions, and lambdas. Default is false.

๐ŸŽ€ readability-redundant-smartptr-get ๐ŸŽ€

Find and remove redundant calls to smart pointerโ€™s .get() method.

Examples:

ptr.get()->Foo()  ==>  ptr->Foo()
*ptr.get()  ==>  *ptr
*ptr->get()  ==>  **ptr
if (ptr.get() == nullptr) ... => if (ptr == nullptr) ...

Options

  • IgnoreMacros โ€” If this option is set to true (default is true), 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().

Options

  • StringParameterFunctions โ€” A semicolon-separated list of regular expressions matching the (fully qualified) names of function/method/operator, with the requirement that any parameter currently accepting a const char* input should also be able to accept std::string inputs, or proper overload candidates that can do so should exist. This can be used to configure functions such as fmt::format, spdlog::logger::info, or wrappers around these and similar functions. The default value is the empty string.

๐ŸŒˆ readability-redundant-string-init ๐ŸŒˆ

Finds unnecessary string initializations.

Examples

// Initializing string with empty string literal is unnecessary.
std::string a = "";
std::string b("");

// becomes

std::string a;
std::string b;

// Initializing a string_view with an empty string literal produces an
// instance that compares equal to string_view().
std::string_view a = "";
std::string_view b("");

// becomes
std::string_view a;
std::string_view b;

Options

  • StringNames โ€” Default is ::std::basic_string;::std::basic_string_view.

Semicolon-delimited list of class names to apply this check to. By default ::std::basic_string applies to std::string and std::wstring. Set to e.g. ::std::basic_string;llvm::StringRef;QString to perform this check on custom classes. ๐ŸŒŸ


๐Ÿงš readability-redundant-typename ๐Ÿงš

Finds redundant uses of the typename keyword.

typename is redundant in two cases. First, before non-dependent names:

/*typename*/ std::vector<int>::size_type size;

And second, since C++20, before dependent names that appear in a context where only a type is allowed (the following example shows just a few of them):

template <typename T>
using trait = /*typename*/ T::type;

template <typename T>
/*typename*/ T::underlying_type as_underlying(T n) {
  return static_cast</*typename*/ T::underlying_type>(n);
}

template <typename T>
struct S {
  /*typename*/ T::type variable;
  /*typename*/ T::type function(/*typename*/ T::type);
};

๐Ÿ‘‘ readability-reference-to-constructed-temporary ๐Ÿ‘‘

Detects C++ code where a reference variable is used to extend the lifetime of a temporary object that has just been constructed.

This construction is often the result of multiple code refactorings or a lack of developer knowledge, leading to confusion or subtle bugs. In most cases, what the developer really wanted to do is create a new variable rather than extending the lifetime of a temporary object.

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. ๐Ÿ’–


โญ readability-simplify-boolean-expr โญ

Looks for boolean expressions involving boolean constants and simplifies them to use the appropriate boolean expression directly. Simplifies boolean expressions by application of DeMorganโ€™s Theorem. Thatโ€™s the whole vibe: DeMorganโ€™s Theorem, periodt. ๐Ÿ’…

okay so here's the glow-up table ๐Ÿ’… โ€” left side is what you wrote, right side is what you get, no cap:

  • 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 โ†’ e
  • e ? false : true โ†’ !e
  • if (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

So the resulting expression e gets a whole makeover, and here's the exact routine:

  1. Unnecessary parentheses around the expression are removed. bye clutter ๐Ÿ’…
  2. Negated applications of ! are eliminated. double-negative era is OVER.
  3. Negated applications of comparison operators are changed to use the opposite condition. flip it, reverse it ๐Ÿฆ‹
  4. Implicit conversions of pointers, including pointers to members, to bool are replaced with explicit comparisons to nullptr in C++11 or NULL in C++98/03. be explicit bestie โœจ
  5. Implicit casts to bool are replaced with explicit casts to bool.
  6. Object expressions with explicit operator bool conversion operators are replaced with explicit casts to bool.
  7. Implicit conversions of integral types to bool are replaced with explicit comparisons to 0.

Examples, and yes we love a demonstration ๐ŸŽ€

  1. The ternary assignment bool b = (i < 0) ? true : false; has redundant parentheses and becomes bool b = i < 0;.
  2. The conditional return if (!b) return false; return true; has an implied double negation and becomes return b;.
  3. The conditional return if (i < 0) return false; return true; becomes return i >= 0;.

The conditional return if (i != 0) return false; return true; becomes return i == 0;.

  1. The conditional return if (p) return true; return false; has an implicit conversion of a pointer to bool and becomes return 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;.

  1. The conditional return if (i & 1) return true; else return false; has an implicit conversion of an integer quantity i & 1 to bool and becomes return (i & 1) != 0;
  2. Given struct X { explicit operator bool(); };, and an instance x of struct X, the conditional return if (x) return true; return false; becomes return static_cast<bool>(x);

โœจ Options โœจ

  • IgnoreMacros โ€” if true, ignore boolean expressions originating from expanded macros. Default is false.
  • ChainedConditionalReturn โ€” if true, conditional boolean return statements at the end of an if/else if chain will be transformed. Default is false.
  • ChainedConditionalAssignment โ€” if true, conditional boolean assignments at the end of an if/else if chain will be transformed. Default is false.
  • SimplifyDeMorgan โ€” if true, DeMorgan's Theorem will be applied to simplify negated conjunctions and disjunctions. Default is true.
  • SimplifyDeMorganRelaxed โ€” if true, SimplifyDeMorgan will also transform negated conjunctions and disjunctions where there is no negation on either operand. This option has no effect if SimplifyDeMorgan is false. Default is false.

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, in which case simply calling operator[] suffice. it's giving extra steps for no reason ๐Ÿ˜ญ

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. like girl, the static member doesn't even know that instance exists ๐Ÿ’€

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. โš ๏ธ so like, read that twice before you let it cook.


๐ŸŽ€ 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. you said it twice bestie, we heard you the first time ๐Ÿ’…

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. iconic, subtractive editing โœจ


๐Ÿ“ 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. fr fr, == is the main character here ๐Ÿ‘‘

Example ๐ŸŒˆ

// The same rules apply to std::string_view.
std::string str1{"a"};
std::string str2{"b"};

and here's the exact hit list of if-statement patterns this check will flag, bestie:

// use str1 != str2 instead.
if (str1.compare(str2)) {
}

// use str1 == str2 instead.
if (!str1.compare(str2)) {
}

// use str1 == str2 instead.
if (str1.compare(str2) == 0) {
}

// use str1 != str2 instead.
if (str1.compare(str2) != 0) {
}

// use str1 == str2 instead.
if (0 == str1.compare(str2)) {
}

// use str1 != str2 instead.
if (0 != str1.compare(str2)) {
}

// Use str1 == "foo" instead.
if (str1.compare("foo") == 0) {
}

Those snippets above are literally the whole warning list: every one of these if-statements gets a warning because it's using compare to check equality or inequality of two strings instead of using the correct operators. No cap. ๐Ÿ’…

โœจ Options โœจ

  • StringLikeClasses โ€” a string containing semicolon-separated names of string-like classes. By default it contains only ::std::basic_string and ::std::basic_string_view. If a class from this list has a compare method similar to that of std::string, it gets checked in the same way. ๐ŸŽ€

๐ŸŒŸ Example ๐ŸŒŸ

struct CustomString {
public:
  int compare (const CustomString& other) const;
}

CustomString str1;
CustomString str2;

// use str1 != str2 instead.
if (str1.compare(str2)) {
}

If StringLikeClasses contains CustomString, the check will suggest replacing compare with equality operator. Slay. โœจ


๐Ÿ•ต๏ธ readability-suspicious-call-argument ๐Ÿ•ต๏ธ

Finds function calls where the arguments passed are provided out of order, based on the difference between the argument name and the parameter names of the function. ๐Ÿ‘€

Given a function call f(foo, bar); and a function signature void f(T tvar, U uvar), the arguments foo and bar are swapped if foo (the argument name) is more similar to uvar (the other parameter) than tvar (the parameter it is currently passed to) and bar is more similar to tvar than uvar. ๐Ÿซถ

Warnings might indicate either that the arguments are swapped, or that the names' cross-similarity might hinder code comprehension. Either way, it's giving "bestie, check that call." ๐Ÿ˜ญ

๐Ÿง  Heuristics ๐Ÿง 

The following heuristics are implemented in the check. If any of the enabled heuristics deem the arguments to be provided out of order, a warning will be issued. ๐Ÿ”ฅ

The heuristics themselves are implemented by considering pairs of strings, and are symmetric, so in the following there is no distinction on which string is the argument name and which string is the parameter name. ๐ŸŒˆ

Equality โ€” the most trivial heuristic, which compares the two strings for case-insensitive equality. ๐Ÿ’…

Abbreviation โ€” common abbreviations can be specified which will deem the strings similar if the abbreviated and the abbreviation stand together. For example, if src is registered as an abbreviation for source, then the following code example will be warned about. ๐Ÿ‘‘

void foo(int source, int x);

foo(b, src);

The abbreviations to recognise can be configured with the Abbreviations check option. This heuristic is case-insensitive. ๐ŸŽ€

Prefix โ€” the prefix heuristic reports if one of the strings is a sufficiently long prefix of the other string, e.g. target to targetPtr. The similarity percentage is the length ratio of the prefix to the longer string, in the previous example, it would be 6 / 9 = 66.66...%. โœจ

This heuristic can be configured with bounds. The default bounds are: below 25% dissimilar and above 30% similar. This heuristic is case-insensitive. ๐ŸŒธ

Suffix โ€” analogous to the Prefix heuristic. In the case of oldValue and value compared, the similarity percentage is 8 / 5 = 62.5%. ๐Ÿฆ‹

This heuristic can be configured with bounds. The default bounds are: below 25% dissimilar and above 30% similar. This heuristic is case-insensitive. ๐Ÿ’œ

Substring โ€” the substring heuristic combines the prefix and the suffix heuristic, and tries to find the longest common substring in the two strings provided. The similarity percentage is the ratio of the found longest common substring against the longer of the two input strings. For example, given val and rvalue, the similarity is 3 / 6 = 50%. If no characters are common in the two string, 0%. ๐Ÿ”Ž

This heuristic can be configured with bounds. The default bounds are: below 40% dissimilar and above 50% similar. This heuristic is case-insensitive. โญ

Levenshtein distance (as Levenshtein) โ€” the Levenshtein distance describes how many single-character changes (additions, changes, or removals) must be applied to transform one string into another. ๐Ÿงš

The Levenshtein distance is translated into a similarity percentage by dividing it with the length of the longer string, and taking its complement with regards to 100%. For example, given something and anything, the distance is 4 edits, and the similarity percentage is 100% - 4 / 9 = 55.55...%. ๐Ÿ”ฅ

This heuristic can be configured with bounds. The default bounds are: below 50% dissimilar and above 66% similar. This heuristic is case-sensitive. ๐Ÿ’…

Jaro--Winkler distance (as JaroWinkler) โ€” the Jaro--Winkler distance is an edit distance like the Levenshtein distance. It is calculated from the amount of common characters that are sufficiently close to each other in position, and to-be-changed characters. The original definition of Jaro has been extended by Winkler to weigh prefix similarities more. The similarity percentage is expressed as an average of the common and non-common characters against the length of both strings. ๐ŸŒˆ

This heuristic can be configured with bounds. The default bounds are: below 75% dissimilar and above 85% similar. This heuristic is case-insensitive. ๐ŸŽ€

Sรธrensen--Dice coefficient (as Dice) โ€” the Sรธrensen--Dice coefficient was originally defined to measure the similarity of two sets. Formally, the coefficient is calculated by dividing 2 * #(intersection) with #(set1) + #(set2), where #() is the cardinality function of sets. This metric is applied to strings by creating bigrams (substring sequences of length 2) of the two strings and using the set of bigrams for the two strings as the two sets. ๐Ÿ“

This heuristic can be configured with bounds. The default bounds are: below 60% dissimilar and above 70% similar. This heuristic is case-insensitive. โœจ

โš™๏ธ Options โš™๏ธ

  • MinimumIdentifierNameLength โ€” sets the minimum required length the argument and parameter names need to have. Names shorter than this length will be ignored. Defaults to 3. ๐ŸŽ€
  • Abbreviations โ€” for the Abbreviation heuristic (see here), this option configures the abbreviations in the "abbreviation=abbreviated_value" format. The option is a string, with each value joined by ";". ๐Ÿฆ‹

By default, the following abbreviations are set:

  • addr=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) is constructed dynamically. In the following, <HeuristicName> refers to one of the keys from the heuristics implemented. ๐Ÿ‘‘

  • <HeuristicName> โ€” True or False, whether a particular heuristic, such as Equality or Levenshtein is enabled. Defaults to True for every heuristic. ๐Ÿ’–
  • <HeuristicName>DissimilarBelow, <HeuristicName>SimilarAbove โ€” a value between 0 and 100, expressing a percentage. The bounds set what percentage of similarity the heuristic must deduce for the two identifiers to be considered similar or dissimilar by the check. ๐ŸŽ€

Given arguments arg1 and arg2 passed to param1 and param2, respectively, the bounds check is performed in the following way: If the similarity of the currently passed argument order (arg1 to param1) is below the DissimilarBelow threshold, and the similarity of the suggested swapped order (arg1 to param2) is above the SimilarAbove threshold, the swap is reported. ๐Ÿ”ฅ

For the defaults of each heuristic, see above. โœจ

๐Ÿงฌ Name synthesis ๐Ÿงฌ

When comparing the argument names and parameter names, the following logic is used to gather the names for comparison: ๐Ÿซถ

Parameter names are the identifiers as written in the source code. ๐Ÿ’…

Argument names are:

  • If a variable is passed, the variable's name. ๐ŸŒธ
  • If a subsequent function call's return value is used as argument, the called function's name. ๐Ÿฆ‹
  • Otherwise, empty string. ๐Ÿ’œ

Empty argument or parameter names are ignored by the heuristics. ๐Ÿ˜ญ


๐ŸŽ€ readability-trailing-comma ๐ŸŽ€

Checks for presence or absence of trailing commas in enum definitions and initializer lists. ๐Ÿ“

The check supports separate policies for single-line and multi-line constructs, allowing different styles for each. By default, the check enforces trailing commas in multi-line constructs and removes them from single-line constructs. โœจ

Trailing commas in multi-line constructs offer several benefits: ๐Ÿ‘‘

  • Adding or removing elements at the end only changes a single line, making diffs smaller and easier to read. ๐Ÿ’–
  • Formatters may change code to a more desired style. ๐ŸŒˆ
  • Code generators avoid the need for special handling of the last element. โญ
// Without trailing commas - adding "Yellow" requires modifying the "Blue" line
enum Color {
  Red,
  Green,
  Blue
};

// With trailing commas - adding "Yellow" is a clean, single-line change
enum Color {
  Red,
  Green,
  Blue,
};

๐Ÿšง Limitations ๐Ÿšง

The check currently doesn't analyze code inside macros. ๐Ÿ™ˆ

โš™๏ธ Options โš™๏ธ

  • SingleLineCommaPolicy โ€” controls whether to add, remove, or ignore trailing commas in single-line enum definitions and initializer lists. Valid values are: ๐ŸŽ€
    • Append: Add trailing commas where missing. โœจ
    • Remove: Remove trailing commas where present. ๐Ÿ’…
    • Ignore: Do not check single-line constructs. ๐Ÿฆ‹

Default is Remove. ๐Ÿ’–

  • MultiLineCommaPolicy โ€” bestie, this one's all about the trailing commas in multi-line enum definitions and initializer lists: are we adding them, yeeting them, or just pretending we don't see them? Valid values are:
    • Append โ€” add trailing commas wherever they're missing.
    • Remove โ€” remove trailing commas wherever they're present.
    • Ignore โ€” don't check multi-line constructs at all, we are minding our own business. ๐Ÿ’…

Default is Append. Slay.


๐Ÿฆ‹ readability-trivial-switch ๐Ÿฆ‹

Finds those lowkey trivial switch statements that could be written way more clearly. Like, every switch statement should have at least two case labels other than a default label โ€” otherwise the switch is just an if wearing a costume, no cap. And switch statements with zero labels? Also diagnosed. ๐Ÿ™ˆ

int i = 42;

switch (i) {
case 1:
  doSomething();
  break;
default:
  doSomethingElse();
  break;
}

// The switch can be written more clearly as:
if (i == 1) {
  doSomething();
} else {
  doSomethingElse();
}

// The switch without any labels will be diagnosed.
int i = 42;
switch (i) {}

๐Ÿงฐ Options ๐Ÿงฐ

  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true.

๐ŸŽ€ readability-uniqueptr-delete-release ๐ŸŽ€

Replace delete <unique_ptr>.release() with <unique_ptr> = nullptr. The latter is shorter, simpler, and honestly just doesn't require you to touch the raw pointer APIs at all. It's giving elevated. โœจ

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.
std::unique_ptr<int> P;
delete P.release();

// becomes

std::unique_ptr<int> P;
P.reset();

๐Ÿ”ฅ readability-uppercase-literal-suffix ๐Ÿ”ฅ

Okay so heads up: cert-dcl16-c redirects here as an alias for this check. By default, only the suffixes that begin with l (l, ll, lu, llu, but not u, ul, ull) are diagnosed by that alias. iykyk.

This check detects when your integral literal or floating point (decimal or hexadecimal) literal has a non-uppercase suffix, and it provides a fix-it hint with the uppercase suffix. All valid combinations of suffixes are supported, periodt.

auto x = 1;  // OK, no suffix.

auto x = 1u; // warning: integer literal suffix 'u' is not upper-case

auto x = 1U; // OK, suffix is uppercase.

...

๐Ÿงฐ Options ๐Ÿงฐ

  • NewSuffixes โ€” optionally, a list of the destination suffixes can be provided. When the suffix is found, a case-insensitive lookup in that list is made, and if a replacement is found that is different from the current suffix, then the diagnostic is issued. This allows for fine-grained control of what suffixes to consider and what their replacements should be. ๐Ÿ‘‘

๐ŸŒธ Example ๐ŸŒธ

Given 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.

๐Ÿงฐ Options (continued) ๐Ÿงฐ

  • IgnoreMacros โ€” if this option is set to true (default is true), the check will not warn about literal suffixes inside macros.

๐ŸŒˆ readability-use-anyofallof ๐ŸŒˆ

Finds range-based for loops that can be replaced by a call to std::any_of or std::all_of. And in C++20 mode? It suggests std::ranges::any_of or std::ranges::all_of instead. Upgrade era. โญ

Example:

bool all_even(const 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; }); (C++20)
// return std::all_of(V.begin(), V.end(), [](int I) { return I % 2 == 0; }); (pre-C++20)

When using a raw initializer list or a temporary range (pre-C++20), it's recommended to materialize it in a local variable first to avoid potential lifetime issues. In C++20, temporary ranges can be used directly with std::ranges algorithms as they handle the lifetime of a temporary range correctly. ๐Ÿซถ

Example with raw initializer list:

bool contains_zero(int a, int b, int c) {
  for (int i : {a, b, c}) {
    if (i == 0)
      return true;
  }
  return false;
}
// Replace loop by
// auto range = {a, b, c};
// return std::ranges::any_of(range, [](int i) { return i == 0; }); (C++20)
// return std::any_of(range.begin(), range.end(), [](int i) { return i == 0; }); (pre-C++20)

Example with temporary range:

extern std::vector<int> get_values();

bool has_even() {
  for (int i : get_values()) {
    if (i % 2 == 0)
      return true;
  }
  return false;
}
// Replace loop by
// return std::ranges::any_of(get_values(), [](int i) { return i % 2 == 0; }); (C++20)
//
// auto values = get_values();
// return std::any_of(values.begin(), values.end(), [](int i) { return i % 2 == 0; }); (pre-C++20)

๐Ÿ’œ readability-use-concise-preprocessor-directives ๐Ÿ’œ

Finds uses of #if that can be simplified to #ifdef or #ifndef and, since C23 and C++23, uses of #elif that can be simplified to #elifdef or #elifndef:

#if defined(MEOW)
#if !defined(MEOW)

// becomes

#ifdef MEOW
#ifndef MEOW

Since C23 and C++23:

#elif defined(MEOW)
#elif !defined(MEOW)

// becomes

#elifdef MEOW
#elifndef MEOW

๐Ÿ“ readability-use-std-min-max ๐Ÿ“

Replaces certain conditional statements with equivalent calls to std::min or std::max.

โš ๏ธ Note: This may impact performance in critical code due to potential additional stores compared to the original if statement. So like, vibe check your hot paths first, bestie. ๐Ÿ˜ญ

Before:

void foo() {
  int a = 2, b = 3;
  if (a < b)
    a = b;
}

After:

void foo() {
  int a = 2, b = 3;
  a = std::max(a, b);
}
  • zircon-temporary-objects
NOTE: the zircon-temporary-objects check is deprecated ๐Ÿ˜ญ and it's getting removed in the 24th release of LLVM, no cap. Please use fuchsia-temporary-objects instead โ€” same energy, not doomed ๐Ÿ’…

Okay bestie, next up is a big ol' table and tables are not the vibe in a terminal, so I'm giving it to you as a list instead ๐Ÿซถ Two things per row: the check Name, and whether it Offers fixes. If I say offers fixes, that's a Yes in the original. If I say nothing about fixes, the column was blank โ€” so no fixes, it's just here to tell you things ๐Ÿ‘‘

  • abseil-cleanup-ctad โ€” offers fixes โœจ
  • abseil-duration-addition โ€” offers fixes โœจ
  • abseil-duration-comparison โ€” offers fixes โœจ
  • abseil-duration-conversion-cast โ€” offers fixes โœจ
  • abseil-duration-division โ€” offers fixes โœจ
  • abseil-duration-factory-float โ€” offers fixes โœจ
  • abseil-duration-factory-scale โ€” offers fixes โœจ
  • abseil-duration-subtraction โ€” offers fixes โœจ
  • abseil-duration-unnecessary-conversion โ€” offers fixes โœจ
  • abseil-faster-strsplit-delimiter โ€” offers fixes โœจ
  • abseil-no-internal-dependencies โ€” no fixes, purely informational ๐Ÿงš
  • abseil-no-namespace โ€” no fixes, purely informational ๐Ÿงš
  • abseil-redundant-strcat-calls โ€” offers fixes โœจ
  • abseil-str-cat-append โ€” offers fixes โœจ
  • abseil-string-find-startswith โ€” offers fixes โœจ
  • abseil-string-find-str-contains โ€” offers fixes โœจ
  • abseil-time-comparison โ€” offers fixes โœจ
  • abseil-time-subtraction โ€” offers fixes โœจ
  • abseil-unchecked-statusor-access โ€” no fixes, purely informational ๐Ÿงš
  • abseil-upgrade-duration-conversions โ€” offers fixes โœจ
  • altera-id-dependent-backward-branch โ€” no fixes, purely informational ๐Ÿงš
  • altera-kernel-name-restriction โ€” no fixes, purely informational ๐Ÿงš
  • altera-single-work-item-barrier โ€” no fixes, purely informational ๐Ÿงš
  • altera-struct-pack-align โ€” offers fixes โœจ
  • altera-unroll-loops โ€” no fixes, purely informational ๐Ÿงš
  • android-cloexec-accept โ€” offers fixes โœจ
  • android-cloexec-accept4 โ€” offers fixes โœจ
  • android-cloexec-creat โ€” offers fixes โœจ
  • android-cloexec-dup โ€” offers fixes โœจ
  • android-cloexec-epoll-create โ€” offers fixes โœจ
  • android-cloexec-epoll-create1 โ€” offers fixes โœจ
  • android-cloexec-fopen โ€” offers fixes โœจ
  • android-cloexec-inotify-init โ€” offers fixes โœจ
  • android-cloexec-inotify-init1 โ€” offers fixes โœจ
  • android-cloexec-memfd-create โ€” offers fixes โœจ
  • android-cloexec-open โ€” offers fixes โœจ
  • android-cloexec-pipe โ€” offers fixes โœจ
  • android-cloexec-pipe2 โ€” bestie, this one's flagged Yes โœจ like yes she's got the fix-it energy, we stan
  • android-cloexec-socket โ€” also Yes ๐Ÿซถ another baddie that auto-slaps the O_CLOEXEC on for you
  • android-comparison-in-temp-failure-retry โ€” no lil' Yes here, she's just out here catching your retry loops being sus ๐Ÿ˜ญ

  • boost-use-ranges โ€” Yes ๐Ÿ’œ fixable and ready to convert your dusty Boost algorithms into ranges, ate
  • boost-use-to-string โ€” Yes ๐ŸŽ€ yes queen, swaps your clunky conversions for boost::to_string

  • bugprone-argument-comment โ€” Yes โญ fixable! checks your /*param=*/ comments actually match the param names, no cap

  • bugprone-assert-side-effect โ€” lowkey iconic, flags side effects hiding inside assert (which vanish in release builds, scary fr ๐Ÿ’€)
  • bugprone-assignment-in-if-condition โ€” the classic if (x = 5) oopsie, caught ๐Ÿ’…
  • bugprone-assignment-in-selection-statement โ€” same energy but for the whole selection statement, not just if ๐Ÿ‘‘
  • bugprone-bad-signal-to-kill-thread โ€” calling pthread_kill with a signal that can't actually kill a thread? nah bestie ๐Ÿ™ˆ
  • bugprone-bitwise-pointer-cast โ€” bitwise-casting pointers, it's giving undefined behaviour, not the vibe ๐ŸŒธ
  • bugprone-bool-pointer-implicit-conversion โ€” Yes ๐ŸŒˆ fixable! bool* silently becoming bool is a trap and she'll patch it for you
  • bugprone-branch-clone โ€” two branches doing the exact same thing? copy-paste detected, main character behaviour though ๐Ÿ’…
  • bugprone-capturing-this-in-member-variable โ€” capturing this when you init a member? that's a dangling heartbreak waiting to happen ๐Ÿ˜ญ
  • bugprone-casting-through-void โ€” casting through void* to dodge the type systemโ€ฆ we see you, and we're telling ๐Ÿ“
  • bugprone-chained-comparison โ€” a < b < c does NOT mean what you think it means bestie, C++ isn't Python โœจ
  • bugprone-command-processor โ€” this one's looking at the deprecated CommandProcessor class, rip ๐Ÿชฆ
  • bugprone-compare-pointer-to-member-virtual-function โ€” comparing a pointer to member against a virtual function pointer? wonky, no cap ๐Ÿฆ‹
  • bugprone-copy-constructor-init โ€” Yes ๐Ÿ’– fixable! your copy ctor forgot to init the base class, lemme fix that for you
  • bugprone-copy-constructor-mutates-argument โ€” a copy ctor that mutates the thing it's copying?? not demure, not mindful ๐Ÿ™ˆ
  • bugprone-crtp-constructor-accessibility โ€” Yes โญ fixable! CRTP base constructors should be protected, and she'll do it for you
  • bugprone-dangling-handle โ€” dangling string_view handles, the โœจiconicโœจ lifetime bug, we love to catch it early
  • bugprone-default-operator-new-on-overaligned-type โ€” default operator new on an over-aligned type? alignment said nope ๐Ÿ’€
  • bugprone-derived-method-shadowing-base-method โ€” your derived method is hiding the base one's whole existence, rude tbh ๐Ÿ‘‘
  • bugprone-dynamic-static-initializers โ€” dynamic static initializers, the static-init-order-fiasco's favourite child ๐Ÿ˜ญ
  • bugprone-easily-swappable-parameters โ€” two params of the same type right next to each other, a swap waiting to ruin your day ๐Ÿ“
  • bugprone-empty-catch โ€” an empty catch {}?? girl what are you swallowing ๐Ÿ˜ญ๐Ÿ’€
  • bugprone-exception-copy-constructor-throws โ€” a copy ctor of an exception type that throws? that's a whole nightmare, fr fr ๐Ÿ™ˆ
  • bugprone-exception-escape โ€” exceptions escaping where they really shouldn't, like main, destructors, and other sacred spots ๐ŸŒธ
  • bugprone-float-loop-counter โ€” for (float f = 0; ...) is giving chaos, precision said no ๐ŸŽ€
  • bugprone-fold-init-type โ€” fold init type mismatch, the accumulation's been lying to you ๐Ÿ’…
  • bugprone-forward-declaration-namespace โ€” that forward declaration lives in a different namespace, awkward โœจ
  • bugprone-forwarding-reference-overload โ€” forwarding refs hijacking your overload resolution, the audacity ๐Ÿ”ฅ
  • bugprone-implicit-widening-of-multiplication-result โ€” Yes ๐Ÿ’– fixable! multiply-then-widen overflow, caught and corrected
  • bugprone-inaccurate-erase โ€” Yes โญ fixable! your erase-remove idiom is doing it wrong, but she's got you
  • bugprone-inc-dec-in-conditions โ€” ++/-- hiding inside a condition is a readability crime, lowkey ๐Ÿ’€
  • bugprone-incorrect-enable-if โ€” Yes ๐Ÿฆ‹ fixable! your enable_if should be using std::enable_if_t or friends, and she'll sort it
  • bugprone-incorrect-enable-shared-from-this โ€” Yes ๐ŸŒˆ fixable! enable_shared_from_this done wrong, now done right
  • bugprone-incorrect-roundings โ€” your "rounding" isn't rounding, bestie, that's just vibes ๐Ÿ˜ญ
  • bugprone-infinite-loop โ€” loop condition never changes so it never ends ๐ŸŒ€ iconic but no, we're fixing this

  • bugprone-integer-division โ€” bestie this one's watching you divide ints and lose the fractional part ๐Ÿ˜ญ
  • bugprone-invalid-enum-default-initialization โ€” lowkey rude to slap a zero onto an enum that never asked for it
  • bugprone-lambda-function-name โ€” the vibes of thinking __func__ inside a lambda tells you the lambda's name? it does not ๐Ÿ’€
  • bugprone-macro-parentheses โ€” Yes โ€” parenthesize your macro args or the preprocessor will humble you, fr fr
  • bugprone-macro-repeated-side-effects โ€” the macro evaluates your expression twice and the second time it's personal
  • bugprone-misleading-setter-of-reference โ€” a setter that looks like it sets but doesn't, it's giving catfish energy ๐Ÿ™ˆ
  • bugprone-misplaced-operator-in-strlen-in-alloc โ€” Yes โ€” classic malloc(strlen(s + 1)) moment, we've all been there ๐Ÿซถ
  • bugprone-misplaced-pointer-arithmetic-in-alloc โ€” Yes โ€” the + 1 goes inside the parens, I promise, iykyk
  • bugprone-misplaced-widening-cast โ€” casting too early and then widening anyway, why would you do that to me ๐Ÿ˜ญ
  • bugprone-missing-end-comparison โ€” Yes โ€” a range that forgot where it ends, iconic in the worst way
  • bugprone-move-forwarding-reference โ€” Yes โ€” std::move on a forwarding reference is a trap, don't take the bait ๐Ÿ’…
  • bugprone-multi-level-implicit-pointer-conversion โ€” pointer to pointer to pointer, so many levels, main character energy on every one
  • bugprone-multiple-new-in-one-expression โ€” two news in one expression and an exception later? leak city, population you ๐Ÿ’€
  • bugprone-multiple-statement-macro โ€” an unbraced macro with several statements, the if you didn't put braces on will simply eat the second one
  • bugprone-narrowing-conversions โ€” big number, small type, the accuracy is justโ€ฆ gone โœจ
  • bugprone-no-escape โ€” ngl this is about pointers not escaping where you thought they did
  • bugprone-non-zero-enum-to-bool-conversion โ€” enum to bool when the enum has more than one nonzero value, that's not a vibe check bestie
  • bugprone-nondeterministic-pointer-iteration-order โ€” iterating a pointer container in unspecified order and pretending it's fine ๐ŸŒ€
  • bugprone-not-null-terminated-result โ€” Yes โ€” you produced a string and forgot the '\0', so now it's just a buffer cosplaying as a string ๐Ÿ˜ญ
  • bugprone-optional-value-conversion โ€” Yes โ€” converting an optional's value in a way that quietly unwraps your whole day
  • bugprone-parent-virtual-call โ€” Yes โ€” calling the parent's virtual and skipping the override, grandparents are being ignored ๐ŸŽ€
  • bugprone-pointer-arithmetic-on-polymorphic-object โ€” pointer math on a polymorphic type, the object size is a mystery bestie, don't
  • bugprone-posix-return โ€” Yes โ€” checking a POSIX return value the wrong way, -1 erasure, we hate to see it
  • bugprone-random-generator-seed โ€” seeding your RNG with something predictable, the โœจchaosโœจ was a lie
  • bugprone-raw-memory-call-on-non-trivial-type โ€” memset on a non-trivial type is giving "I didn't read the constructor" ๐Ÿซฃ
  • bugprone-redundant-branch-condition โ€” Yes โ€” a branch condition that was already guaranteed, girl we already knew ๐Ÿ’…
  • bugprone-reserved-identifier โ€” Yes โ€” double underscores and friends are reserved, that name is not yours to take, periodt
  • bugprone-return-const-ref-from-parameter โ€” returning a const ref to a by-value parameter, uh, that reference is about to expire ๐Ÿ˜ญ
  • bugprone-shared-ptr-array-mismatch โ€” Yes โ€” shared_ptr and arrays getting their wires crossed, the deleter is confused
  • bugprone-signal-handler โ€” signal handlers are a whole thing, what you're allowed to call in one is basically nothing ๐Ÿ”ฅ
  • bugprone-signed-bitwise โ€” bitwise ops on signed values, implementation-defined, my beloved nightmare
  • bugprone-signed-char-misuse โ€” char signedness jumping out from behind a bush when you least expect it ๐Ÿฆ‹
  • bugprone-sizeof-container โ€” sizeof a container and got the object size, not the element count. ma'am that's not size() ๐Ÿ˜ญ
  • bugprone-sizeof-expression โ€” sizeof misuse in expressions, the numbers are lying to you
  • bugprone-spuriously-wake-up-functions โ€” spurious wakeups are real, loop your condition variable, don't trust a single wait
  • bugprone-standalone-empty โ€” Yes โ€” a stray empty sitting there doing nothing, she's on her own little journey ๐Ÿงš
  • bugprone-std-exception-baseclass โ€” catching the base exception class when you meant the specific one, all your errors are now one error ๐ŸŒˆ
  • bugprone-std-namespace-modification โ€” adding things to std:: is highkey not allowed, don't touch, that's not your namespace ๐Ÿ’€
  • bugprone-string-constructor โ€” Yes โ€” std::string constructed with the wrong arguments, the classic (ptr, char) jumpscare
  • bugprone-string-integer-assignment โ€” Yes โ€” assigning an integer to a string, you just got a character code. s = 65 is not "65" bestie ๐Ÿ˜ญ
  • bugprone-string-literal-with-embedded-nul โ€” a string literal with a '\0' in the middle, the rest of it is ghosted, and sizeof will still count it ๐Ÿ’€

  • bugprone-stringview-nullptr โ€” Yes โœจ bestie this one comes pre-loaded, no config needed
  • bugprone-suspicious-enum-usage โ€” not marked, so it's a you-opt-in situation ๐Ÿ™ˆ
  • bugprone-suspicious-include โ€” also unmarked, lurking quietly, waiting for you to invite it in
  • bugprone-suspicious-memory-comparison โ€” no Yes here either, just the audacity of the name
  • bugprone-suspicious-memset-usage โ€” Yes ๐Ÿ’€ the name alone is giving "I found something cursed in your code"
  • bugprone-suspicious-missing-comma โ€” unmarked, but the name is a whole jumpscare fr fr
  • bugprone-suspicious-realloc-usage โ€” not enabled by default, tragically
  • bugprone-suspicious-semicolon โ€” Yes ๐ŸŽ€ iconic, legendary, the semicolon detective stays on
  • bugprone-suspicious-string-compare โ€” Yes โœจ yes bestie, the string comparison sussy check is always watching
  • bugprone-suspicious-stringview-data-usage โ€” no Yes, optional girly
  • bugprone-swapped-arguments โ€” Yes ๐Ÿ”ฅ the classic "you put the args in backwards" catch, on by default, thank god
  • bugprone-switch-missing-default-case โ€” no marker, so it's chilling off by default
  • bugprone-tagged-union-member-count โ€” unmarked too ๐ŸŒธ
  • bugprone-terminating-continue โ€” Yes ๐Ÿ’… yeah this one's live by default
  • bugprone-throw-keyword-missing โ€” not marked, opt-in only
  • bugprone-throwing-static-initialization โ€” unmarked ๐Ÿฆ‹
  • bugprone-too-small-loop-variable โ€” unmarked, lowkey a shame ngl
  • bugprone-unchecked-optional-access โ€” no Yes ๐Ÿซถ
  • bugprone-unchecked-string-to-number-conversion โ€” also unmarked, wild but okay
  • bugprone-undefined-memory-manipulation โ€” unmarked ๐Ÿ˜ญ the name is so ominous and yet it's off by default
  • bugprone-undelegated-constructor โ€” no marker, bring it in yourself
  • bugprone-unhandled-code-paths โ€” unmarked ๐ŸŒˆ
  • bugprone-unhandled-exception-at-new โ€” unmarked too
  • bugprone-unhandled-self-assignment โ€” no Yes, your self-assignment drama is your own business
  • bugprone-unintended-char-ostream-output โ€” Yes โญ live by default, catching those weird little chars you didn't mean to print
  • bugprone-unique-ptr-array-mismatch โ€” Yes ๐Ÿ‘‘ default-on, and it should be
  • bugprone-unsafe-functions โ€” unmarked, opt-in ๐Ÿ’œ
  • bugprone-unsafe-to-allow-exceptions โ€” unmarked as well
  • bugprone-unused-local-non-trivial-variable โ€” no Yes ๐Ÿ“
  • bugprone-unused-raii โ€” Yes โœจ default-enabled, watching your RAII moments like a hawk
  • bugprone-unused-return-value โ€” unmarked, sorry bestie
  • bugprone-use-after-move โ€” unmarked too ๐Ÿ˜ญ iconic check, not default, that's just how it is
  • bugprone-virtual-near-miss โ€” Yes ๐Ÿ’€ default-on and it will clock your almost-override
  • concurrency-mt-unsafe โ€” no marker, opt-in ๐Ÿงš
  • concurrency-thread-canceltype-asynchronous โ€” unmarked
  • cppcoreguidelines-avoid-capturing-lambda-coroutines โ€” unmarked ๐ŸŒธ
  • cppcoreguidelines-avoid-const-or-ref-data-members โ€” unmarked
  • cppcoreguidelines-avoid-do-while โ€” no Yes ๐ŸŽ€
  • cppcoreguidelines-avoid-goto โ€” unmarked, and honestly the guideline girlies know why
  • cppcoreguidelines-avoid-non-const-global-variables โ€” unmarked ๐Ÿฆ‹

okay so the pattern here is chef's kiss: the ones with Yes are just already in your lineup, and the rest are sitting there waiting for you to add them to your config yourself ๐Ÿ’– no drama, no cap โœจ

Still rolling through the check table, bestie โ€” grab a snack, we're in the cppcoreguidelines trenches and honestly it's giving "the professor who assigns 40 readings per week" energy ๐Ÿ˜ญ. Same deal as before: check name on the left, and a big Yes on the right for the ones that got the nod ๐Ÿ’…. Blank means blank.

  • cppcoreguidelines-avoid-reference-coroutine-parameters โ€” nothing in the Yes column, the row is just out here vibing ๐Ÿฆ‹
  • cppcoreguidelines-init-variables โ€” Yes โœจ
  • cppcoreguidelines-interfaces-global-init โ€” no Yes here ๐Ÿ™ˆ
  • cppcoreguidelines-macro-usage โ€” also blank in the Yes column
  • cppcoreguidelines-misleading-capture-default-by-value โ€” Yes ๐Ÿ’–
  • cppcoreguidelines-missing-std-forward โ€” blank, no Yes
  • cppcoreguidelines-no-malloc โ€” blank, no Yes ๐Ÿ“
  • cppcoreguidelines-no-suspend-with-lock โ€” blank, no Yes
  • cppcoreguidelines-owning-memory โ€” blank, no Yes
  • cppcoreguidelines-prefer-member-initializer โ€” Yes ๐Ÿ‘‘
  • cppcoreguidelines-pro-bounds-array-to-pointer-decay โ€” blank, no Yes
  • cppcoreguidelines-pro-bounds-avoid-unchecked-container-access โ€” Yes ๐Ÿ”ฅ
  • cppcoreguidelines-pro-bounds-constant-array-index โ€” Yes ๐ŸŒˆ
  • cppcoreguidelines-pro-bounds-pointer-arithmetic โ€” blank, no Yes
  • cppcoreguidelines-pro-type-const-cast โ€” blank, no Yes
  • cppcoreguidelines-pro-type-cstyle-cast โ€” Yes ๐Ÿ’€
  • cppcoreguidelines-pro-type-member-init โ€” Yes ๐ŸŒธ
  • cppcoreguidelines-pro-type-reinterpret-cast โ€” blank, no Yes
  • cppcoreguidelines-pro-type-static-cast-downcast โ€” Yes โญ
  • cppcoreguidelines-pro-type-union-access โ€” blank, no Yes
  • cppcoreguidelines-pro-type-vararg โ€” blank, no Yes
  • cppcoreguidelines-rvalue-reference-param-not-moved โ€” blank, no Yes
  • cppcoreguidelines-slicing โ€” blank, no Yes
  • cppcoreguidelines-special-member-functions โ€” blank, no Yes
  • cppcoreguidelines-use-enum-class โ€” blank, no Yes
  • cppcoreguidelines-virtual-class-destructor โ€” Yes ๐Ÿงš

Okay and that's the whole cppcoreguidelines family done โ€” we survived, barely ๐Ÿ˜ญ. Next up, the platform-specific girlies:

  • darwin-avoid-spinlock โ€” blank, no Yes
  • darwin-dispatch-once-nonstatic โ€” Yes ๐ŸŽ€

Then the fuchsia section, which lowkey has a whole saga of its own going on:

  • fuchsia-default-arguments-calls โ€” blank, no Yes
  • fuchsia-default-arguments-declarations โ€” Yes ๐Ÿ’œ
  • fuchsia-overloaded-operator โ€” blank, no Yes
  • fuchsia-statically-constructed-objects โ€” blank, no Yes
  • fuchsia-temporary-objects โ€” blank, no Yes
  • fuchsia-trailing-return โ€” blank, no Yes
  • fuchsia-virtual-inheritance โ€” blank, no Yes

And finally the google crew creeping in, including two Objective-C ones because of course ๐Ÿซถ:

  • google-build-explicit-make-pair โ€” blank, no Yes
  • google-build-using-namespace โ€” blank, no Yes
  • google-default-arguments โ€” blank, no Yes
  • google-global-names-in-headers โ€” blank, no Yes
  • google-objc-avoid-nsobject-new โ€” blank, no Yes
  • google-objc-avoid-throwing-exception โ€” blank, no Yes

Not me ending this chunk on a run of blanks ๐Ÿ’€ โ€” the table keeps going though, so don't you dare close the terminal yet โœจ

  • google-objc-function-naming โ€” still in the google- block, no Yes here bestie ๐Ÿ’…

  • google-objc-global-variable-declaration โ€” another ObjC one, also no Yes in that column

  • google-readability-avoid-underscore-in-googletest-name โ€” yes the name is literally that long, she ate and left no crumbs ๐ŸŽ€

  • google-readability-todo โ€” ok lowkey the name is a whole vibe check on its own

  • google-runtime-float

  • google-runtime-int

  • google-runtime-operator

  • google-upgrade-googletest-case โ€” Yes โœจ marked in the column, she's on the list

  • linuxkernel-must-check-errs โ€” the kernel one, sound the alarm ๐Ÿšจ

  • llvm-formatv-string โ€” and now we've slid into the llvm- era of the list, periodt

  • llvm-header-guard

  • llvm-include-order โ€” Yes ๐Ÿซถ

  • llvm-namespace-comment

  • llvm-prefer-isa-or-dyn-cast-in-conditionals โ€” Yes โญ (long name, iconic behaviour, we love a descriptive queen)

  • llvm-prefer-register-over-unsigned โ€” Yes ๐Ÿ’–

  • llvm-prefer-static-over-anonymous-namespace โ€” no Yes on this one, noted ๐Ÿฆ‹

  • llvm-redundant-casting โ€” Yes ๐Ÿ”ฅ

  • llvm-twine-local โ€” Yes ๐ŸŒธ

  • llvm-type-switch-case-types โ€” Yes ๐Ÿ’œ

  • llvm-use-new-mlir-op-builder โ€” Yes ๐Ÿ‘‘

  • llvm-use-ranges โ€” Yes ๐ŸŒˆ

  • llvm-use-vector-utils โ€” Yes โœจ that's the last of the plain llvm- ones, we're crossing over into llvmlibc- territory now

  • llvmlibc-callee-namespace โ€” no Yes column action here

  • llvmlibc-implementation-in-namespace โ€” also blank in the column, keep scrolling bestie

  • llvmlibc-inline-function-decl โ€” Yes ๐ŸŽ€

  • llvmlibc-restrict-system-libc-headers โ€” Yes ๐Ÿ’€ (long name, big energy)

  • misc-anonymous-namespace-in-header โ€” and now we're in the misc- club, the biggest group of the whole list fr fr

  • misc-confusable-identifiers

  • misc-const-correctness โ€” Yes ๐ŸŒŸ

  • misc-coroutine-hostile-raii โ€” such a dramatic name, love that for her ๐Ÿ™ˆ no Yes though

  • misc-definitions-in-headers โ€” Yes ๐Ÿงš

  • misc-explicit-constructor โ€” Yes ๐Ÿ’…

  • misc-header-include-cycle โ€” no Yes, just vibes

  • misc-include-cleaner โ€” Yes ๐Ÿ“

  • misc-misleading-bidirectional

  • misc-misleading-identifier

  • misc-misplaced-const

  • misc-multiple-inheritance

  • misc-new-delete-overloads

  • misc-no-recursion

  • misc-non-copyable-objects โ€” and that's where this chunk of the list lands, we're still mid-run so don't go anywhere ๐Ÿ’–
  • misc-non-private-member-variables-in-classes โ€” the check that side-eyes your member variables living out loud, no privacy era ๐Ÿ’…
  • misc-override-with-different-visibility โ€” catches an override that switched up its visibility on you ๐Ÿ‘€
  • misc-predictable-rand โ€” because your rand() being predictable is NOT the plot twist we asked for ๐Ÿ˜ญ

  • misc-redundant-expression โ€” Yes โœ… saying the same thing twice is giving redundant, bestie
  • misc-static-assert โ€” Yes โœ… static_assert glow-up, on by default โœจ
  • misc-static-initialization-cycle โ€” for when your statics are in a situationship with each other ๐ŸŒ€
  • misc-throw-by-value-catch-by-reference โ€” the throw/catch combo that just makes sense ๐ŸŽ€
  • misc-unconventional-assign-operator โ€” your operator= is doing too much, and not in the iconic way
  • misc-uniqueptr-reset-release โ€” Yes โœ… unique_ptr reset/release shenanigans, flagged by default
  • misc-unused-alias-decls โ€” Yes โœ… alias declarations nobody's using, we see you ๐Ÿ™ˆ
  • misc-unused-parameters โ€” Yes โœ… parameters that are justโ€ฆ there. vibes only, zero function ๐Ÿ’€
  • misc-unused-using-decls โ€” Yes โœ… using declarations collecting dust
  • misc-use-anonymous-namespace โ€” anonymous namespace agenda ๐Ÿซถ
  • misc-use-internal-linkage โ€” Yes โœ… internal linkage, on by default, main character energy ๐Ÿ”ฅ

๐Ÿฆ‹ the modernize- girlies ๐Ÿฆ‹

  • modernize-avoid-bind โ€” Yes โœ… bind is so 2011, bestie
  • modernize-avoid-c-arrays โ€” raw C arrays? in THIS economy? ๐Ÿ˜ญ
  • modernize-avoid-c-style-cast โ€” Yes โœ… C-style casts are cancelled, and this one's on by default
  • modernize-avoid-setjmp-longjmp โ€” setjmp/longjmp said "trust me bro" and we said no thank you
  • modernize-avoid-variadic-functions โ€” variadic functions catching strays, but honestly fair ๐Ÿซ 
  • modernize-concat-nested-namespaces โ€” Yes โœ… nested namespaces, concatenated, slay โœจ
  • modernize-deprecated-headers โ€” Yes โœ… deprecated headers found and clocked
  • modernize-deprecated-ios-base-aliases โ€” Yes โœ… those deprecated ios base aliases, yes ma'am
  • modernize-loop-convert โ€” Yes โœ… turn it into a range-based loop, you'll feel better ๐Ÿ’–
  • modernize-macro-to-enum โ€” Yes โœ… your #defines want to be enums when they grow up
  • modernize-make-shared โ€” Yes โœ… make_shared, iconic behaviour
  • modernize-make-unique โ€” Yes โœ… make_unique, equally iconic ๐Ÿ‘‘
  • modernize-min-max-use-initializer-list โ€” Yes โœ… min/max with an initializer list, we love to see it
  • modernize-pass-by-value โ€” Yes โœ… pass by value era ๐Ÿ’ซ
  • modernize-raw-string-literal โ€” Yes โœ… raw string literals, no more escape-room energy
  • modernize-redundant-void-arg โ€” Yes โœ… that void in the arg list is doing nothing, respectfully
  • modernize-replace-auto-ptr โ€” Yes โœ… auto_ptrโ€ฆ babe, it's time ๐Ÿ•ฐ๏ธ
  • modernize-replace-disallow-copy-and-assign-macro โ€” Yes โœ… that macro has a replacement and it's serving
  • modernize-replace-random-shuffle โ€” Yes โœ… the random shuffle glow-up, on by default
  • modernize-return-braced-init-list โ€” Yes โœ… braces in the return, cute, correct, done ๐ŸŽ€
  • modernize-shrink-to-fit โ€” Yes โœ… shrink to fit, the way that it just fits ๐Ÿฆ‹
  • modernize-type-traits โ€” Yes โœ… type traits update, look at her go โœจ
  • modernize-unary-static-assert โ€” Yes โœ… unary static_assert, yes and yes
  • modernize-use-auto โ€” Yes โœ… auto!! literally the whole personality of this list ๐ŸŒˆ

okay bestie we are still in the list and we have officially entered the modernize- era ๐Ÿ’… quick note before you scroll: if a check has Yes next to it, that's literally the second column as printed โ€” and if there's no Yes, that column is just blank, and I'm not gonna invent anything to fill it, promise ๐Ÿซถ

  • modernize-use-bool-literals โ€” Yes
  • modernize-use-constraints โ€” Yes
  • modernize-use-default-member-init โ€” Yes
  • modernize-use-designated-initializers โ€” Yes
  • modernize-use-emplace โ€” Yes
  • modernize-use-equals-default โ€” Yes
  • modernize-use-equals-delete โ€” Yes
  • modernize-use-integer-sign-comparison โ€” Yes
  • modernize-use-nodiscard โ€” Yes
  • modernize-use-noexcept โ€” Yes
  • modernize-use-nullptr โ€” Yes
  • modernize-use-override โ€” Yes
  • modernize-use-ranges โ€” Yes
  • modernize-use-scoped-lock โ€” Yes
  • modernize-use-starts-ends-with โ€” Yes
  • modernize-use-std-bit โ€” Yes
  • modernize-use-std-format โ€” Yes
  • modernize-use-std-numbers โ€” Yes
  • modernize-use-std-print โ€” Yes
  • modernize-use-string-view โ€” Yes
  • modernize-use-structured-binding โ€” Yes
  • modernize-use-trailing-return-type โ€” Yes
  • modernize-use-transparent-functors โ€” Yes
  • modernize-use-uncaught-exceptions โ€” Yes
  • modernize-use-using โ€” Yes

like, that whole block is alphabetized and every single one is a Yes โ€” the modernize squad ate and left no crumbs ๐Ÿ‘‘โœจ


and now we pivot to other prefixes, because the alphabet said so ๐Ÿฆ‹

  • mpi-buffer-deref โ€” Yes
  • mpi-type-mismatch โ€” Yes

two mpi- checks, both Yes, both iconic in their own quiet way ๐ŸŽ€

  • objc-assert-equals โ€” Yes
  • objc-avoid-nserror-init โ€” (no mark in that column)
  • objc-dealloc-in-category โ€” (no mark in that column)
  • objc-forbidden-subclassing โ€” (no mark in that column)
  • objc-missing-hash โ€” (no mark in that column)
  • objc-nsdate-formatter โ€” (no mark in that column)
  • objc-nsinvocation-argument-lifetime โ€” Yes
  • objc-property-declaration โ€” Yes
  • objc-super-self โ€” Yes

the objc- run is giving mixed signals and honestly? relatable ๐Ÿ˜ญ some have the Yes, some are just vibing with an empty cell โ€” that's the data, I'm just reporting it ๐Ÿ’€

  • openmp-exception-escape โ€” (no mark in that column)
  • openmp-use-default-none โ€” (no mark in that column)

two openmp- checks, both blank in that column, no notes, moving on ๐ŸŒธ

  • performance-avoid-endl โ€” Yes

and we close this chunk on performance-avoid-endl with a Yes, which is a slay way to end a stretch of the list ngl ๐Ÿ”ฅ

still not done though โ€” more of the page is coming, so don't go anywhere bestie ๐Ÿ’œ

  • performance-enum-size โ€” keep an eye on your enums so they're not lowkey huge for no reason ๐ŸŽ€
  • performance-for-range-copy โ€” Yes โœจ the default squad said yes to flagging copies hiding in your range-for loops
  • performance-implicit-conversion-in-loop โ€” catches sneaky implicit conversions chillin' inside your loop body ๐Ÿ™ˆ
  • performance-inefficient-algorithm โ€” Yes ๐Ÿ”ฅ calls out the algorithm choices that are giving "why would you do that" energy
  • performance-inefficient-string-concatenation โ€” the string-concat glow-down detector, bestie ๐Ÿ’…
  • performance-inefficient-vector-operation โ€” Yes โญ vector ops that could've been one call and weren't
  • performance-move-const-arg โ€” Yes ๐ŸŒธ that std::move on a const arg isn't doing what you think, queen
  • performance-move-constructor-init โ€” watch how you init those members in move constructors
  • performance-no-automatic-move โ€” flags the spots where automatic move justโ€ฆ didn't happen ๐Ÿ˜ญ
  • performance-no-int-to-ptr โ€” ints are not pointers, periodt ๐Ÿซถ
  • performance-noexcept-destructor โ€” Yes ๐Ÿ’œ destructors should be noexcept, and this check is obsessed with making sure they are
  • performance-noexcept-move-constructor โ€” Yes ๐Ÿ”ฅ same vibe, but for move constructors
  • performance-noexcept-swap โ€” Yes ๐Ÿฆ‹ swap functions get the noexcept treatment too
  • performance-prefer-single-char-overloads โ€” Yes ๐Ÿ‘‘ prefer the single-char overload, it's the main character
  • performance-string-view-conversions โ€” Yes โœจ conversions to string_view that are giving unnecessary-work
  • performance-trivially-destructible โ€” Yes ๐ŸŒˆ mark those types trivially destructible, it's free real estate
  • performance-type-promotion-in-math-fn โ€” Yes ๐Ÿ“ that implicit promotion in your math function call? caught
  • performance-unnecessary-copy-initialization โ€” Yes ๐Ÿ’– copies on init that did not need to happen, ngl
  • performance-unnecessary-value-param โ€” Yes ๐ŸŽ€ value params that should've been refs, we've all been there
  • performance-use-std-move โ€” Yes ๐Ÿ’€ the "bestie you forgot the std::move" check
  • portability-avoid-pragma-once โ€” avoid #pragma once, it's not the vibe for portability ๐ŸŒ
  • portability-no-assembler โ€” inline assembler said no to portability, so this check says no too
  • portability-restrict-system-includes โ€” Yes ๐ŸŒธ restrict those system includes, keep it tight
  • portability-simd-intrinsics โ€” SIMD intrinsics and portability are in a situationship
  • portability-std-allocator-const โ€” that const on a std::allocator got flagged
  • portability-template-virtual-member-function โ€” virtual member functions in templates, portability said ๐Ÿซ 

  • readability-ambiguous-smartptr-reset-call โ€” Yes ๐Ÿ’… that .reset() call is ambiguous and we're calling it out
  • readability-avoid-const-params-in-decls โ€” Yes โœจ const on params in declarations? take it out of the decl
  • readability-avoid-nested-conditional-operator โ€” nested ternariesโ€ฆ girl ๐Ÿ˜ญ
  • readability-avoid-return-with-void-value โ€” Yes ๐Ÿ”ฅ don't return a void value, it's giving confusing
  • readability-avoid-unconditional-preprocessor-if โ€” an #if with no condition? iconic but no
  • readability-braces-around-statements โ€” Yes ๐ŸŽ€ braces around your statements, always, no cap
  • readability-const-return-type โ€” Yes ๐Ÿ’œ const on a return type is usually not the move
  • readability-container-contains โ€” Yes ๐ŸŒˆ use contains, it's the moment
  • readability-container-data-pointer โ€” Yes โญ the &container[0] to .data() glow-up
  • readability-container-size-empty โ€” Yes ๐Ÿฆ‹ size() == 0 โ†’ empty(), we love a clean check
  • readability-convert-member-functions-to-static โ€” Yes ๐Ÿ‘‘ if it doesn't touch this, it's a static function, periodt
  • readability-delete-null-pointer โ€” Yes ๐Ÿ’€ deleting a null pointer is a no-op, so literally why
  • readability-duplicate-include โ€” Yes ๐Ÿ“ the same include twice? bestie the compiler's already seen it
  • readability-else-after-return โ€” โœ… Yes. bestie if you're doing else right after a return we need to talk ๐Ÿ’… it's on the list.
  • readability-enum-initial-value โ€” โœ… Yes. give your enums an initial value, icon behaviour.
  • readability-function-cognitive-complexity โ€” no โœ… in the column here, just vibes ๐Ÿ™ˆ the way this one measures how hard your function is to think about? yeah.
  • readability-function-size โ€” no โœ…. it's giving "your function is too long and I'm tired" energy.
  • readability-identifier-length โ€” no โœ…. yes it WILL come for your single-letter variable names and honestly? fair.
  • readability-identifier-naming โ€” โœ… Yes. naming conventions, the whole aesthetic ๐ŸŽ€
  • readability-implicit-bool-conversion โ€” โœ… Yes. no sneaky implicit bools, we don't do that here.
  • readability-inconsistent-declaration-parameter-name โ€” โœ… Yes. when the declaration and the definition disagree on a parameter name ๐Ÿ˜ญ the audacity.
  • readability-inconsistent-ifelse-braces โ€” โœ… Yes. braces on one branch and not the other? pick a lane, bestie.
  • readability-isolate-declaration โ€” โœ… Yes. one declaration per statement, main character clarity โœจ
  • readability-magic-numbers โ€” no โœ…. those mysterious numbers sitting in your code with zero explanation ๐Ÿ’€ not marked here tho.
  • readability-make-member-function-const โ€” โœ… Yes. if it can be const, it should be const, periodt.
  • readability-math-missing-parentheses โ€” โœ… Yes. parentheses in math expressions, because precedence is a trap ๐Ÿ™ˆ
  • readability-misleading-indentation โ€” no โœ…. indentation that lies to you is genuinely the worst kind of betrayal ๐Ÿ’€
  • readability-misplaced-array-index โ€” โœ… Yes. index in the wrong spot? caught.
  • readability-named-parameter โ€” โœ… Yes. name your parameters, they deserve an identity ๐Ÿซถ
  • readability-non-const-parameter โ€” โœ… Yes. pointer/reference parameters that could be const but aren't ๐ŸŒธ
  • readability-operators-representation โ€” โœ… Yes. keep your operator spellings consistent, okurrr.
  • readability-qualified-auto โ€” โœ… Yes. auto with its qualifiers, slay.
  • readability-redundant-access-specifiers โ€” โœ… Yes. repeating public: when you already said public:? not the flex you think it is.
  • readability-redundant-casting โ€” โœ… Yes. casting to the type it already is ๐Ÿ˜ญ babe.
  • readability-redundant-control-flow โ€” โœ… Yes. return; at the end of a void function, we see you ๐Ÿ‘€
  • readability-redundant-declaration โ€” โœ… Yes. declaring the same thing twice, iconic but no.
  • readability-redundant-function-ptr-dereference โ€” โœ… Yes. you don't need to dereference that function pointer, bestie ๐Ÿ’…
  • readability-redundant-inline-specifier โ€” โœ… Yes. inline where it's already implied? redundant, king.
  • readability-redundant-lambda-parameter-list โ€” โœ… Yes. empty () on a lambda that doesn't need it โ€” drop it โœจ
  • readability-redundant-member-init โ€” โœ… Yes. member init that duplicates what's already happening, we're over it.
  • readability-redundant-nested-if โ€” โœ… Yes. nested ifs that could just be one if ๐Ÿฆ‹ flatten it.
  • readability-redundant-parentheses โ€” โœ… Yes. extra parens are not a personality, sorry not sorry ๐Ÿ™ˆ
  • readability-redundant-preprocessor โ€” no โœ…. redundant preprocessor stuff, justโ€ฆ no. blank column.
  • readability-redundant-qualified-alias โ€” โœ… Yes. overly qualified alias names, we're simplifying.
  • readability-redundant-smartptr-get โ€” โœ… Yes. calling .get() when you don't have to? put it down ๐Ÿซถ
  • readability-redundant-string-cstr โ€” โœ… Yes. .c_str() where it's not needed, no cap.
  • readability-redundant-string-init โ€” โœ… Yes. std::string s = ""; โ€” girl just default-construct it ๐Ÿ’€
  • readability-redundant-typename โ€” โœ… Yes. a typename that isn't doing anything, bye.
  • readability-reference-to-constructed-temporary โ€” no โœ…. a reference to a constructed temporary โ€” highkey the kind of thing that bites you later ๐Ÿ˜ญ not marked yes here.
  • readability-simplify-boolean-expr โ€” โœ… Yes. boolean expressions that need simplifying, ate.
  • readability-simplify-subscript-expr โ€” โœ… Yes. subscript expressions that could be simpler, yes ma'am โญ
  • readability-static-accessed-through-instance โ€” offers fixes โœ…
  • readability-static-definition-in-anonymous-namespace โ€” offers fixes โœ…
  • readability-string-compare โ€” offers fixes โœ…
  • readability-suspicious-call-argument โ€” that's a no on the fixes column, bestie
  • readability-trailing-comma โ€” offers fixes โœ…
  • readability-trivial-switch โ€” no fixes marked here
  • readability-uniqueptr-delete-release โ€” offers fixes โœ…
  • readability-uppercase-literal-suffix โ€” offers fixes โœ…
  • readability-use-anyofallof โ€” nope, no fixes marked
  • readability-use-concise-preprocessor-directives โ€” offers fixes โœ…
  • readability-use-std-min-max โ€” offers fixes โœ…
  • zircon-temporary-objects โ€” no fixes marked (she's just here for the vibes ๐Ÿ’…)

๐ŸŽ€ Check aliases ๐ŸŽ€

Okay so this next bit is lowkey the most useful table on the whole page โ€” it's the alias list! Old names on the left, the check they actually redirect to on the right, plus whether that check offers fixes. Iykyk, because if you've been copy-pasting cert-* names into your .clang-tidy config since forever, this is the glow-up translation guide you need. โœจ

  • cert-arr39-c โ†’ redirects to bugprone-sizeof-expression
  • cert-con36-c โ†’ redirects to bugprone-spuriously-wake-up-functions
  • cert-con54-cpp โ†’ redirects to bugprone-spuriously-wake-up-functions
  • cert-ctr56-cpp โ†’ redirects to bugprone-pointer-arithmetic-on-polymorphic-object
  • cert-dcl03-c โ†’ redirects to misc-static-assert โ€” and yes, it offers fixes โœ…
  • cert-dcl16-c โ†’ redirects to readability-uppercase-literal-suffix โ€” offers fixes โœ…
  • cert-dcl37-c โ†’ redirects to bugprone-reserved-identifier โ€” offers fixes โœ…
  • cert-dcl50-cpp โ†’ redirects to modernize-avoid-variadic-functions
  • cert-dcl51-cpp โ†’ redirects to bugprone-reserved-identifier โ€” offers fixes โœ…
  • cert-dcl54-cpp โ†’ redirects to misc-new-delete-overloads
  • cert-dcl58-cpp โ†’ redirects to bugprone-std-namespace-modification
Bestie note: two of these โ€” cert-con36-c and cert-con54-cpp โ€” both funnel into the exact same check, bugprone-spuriously-wake-up-functions. Not a typo, that's literally just how it's written. Also cert-dcl37-c and cert-dcl51-cpp both land on bugprone-reserved-identifier. So if you're wondering why your config feels like it's doing double duty, it kind of is. ๐Ÿ˜ญ

And the way the fixes column works across this whole alias table: only the rows marked "Yes" offer fixes โ€” misc-static-assert, readability-uppercase-literal-suffix, and bugprone-reserved-identifier (twice). The rest are vibes-only, no autofix. ๐Ÿซถ

  • cert-dcl59-cpp โ†’ misc-anonymous-namespace-in-header ๐ŸŽ€ โ€” the CERT rule, and the clang-tidy check that's doing the actual work behind it.
  • cert-env33-c โ†’ bugprone-command-processor โœจ โ€” same deal, the check is the one holding it down.
  • cert-err09-cpp โ†’ misc-throw-by-value-catch-by-reference ๐Ÿฆ‹ โ€” mapping, slayed.
  • cert-err33-c โ†’ bugprone-unused-return-value ๐Ÿ’– โ€” yes, the return value one. it's giving "girl, look at what you're ignoring."
  • cert-err34-c โ†’ bugprone-unchecked-string-to-number-conversion ๐ŸŒธ โ€” no cap, this is the pairing.
  • cert-err52-cpp โ†’ modernize-avoid-setjmp-longjmp ๐Ÿ”ฅ โ€” icon behaviour really.
  • cert-err58-cpp โ†’ bugprone-throwing-static-initialization โญ โ€” that's the check, bestie.
  • cert-err60-cpp โ†’ bugprone-exception-copy-constructor-throws ๐Ÿ’… โ€” long name, but we respect her.
  • cert-err61-cpp โ†’ misc-throw-by-value-catch-by-reference ๐Ÿ˜ญ โ€” yeah she shows up twice in this list, and honestly? she earned it.
  • cert-exp42-c โ†’ bugprone-suspicious-memory-comparison ๐Ÿ‘‘ โ€” suspicious indeed.
  • cert-exp45-c โ†’ bugprone-assignment-in-selection-statement ๐Ÿซถ โ€” the assignment-in-selection one, exactly as written.
  • cert-fio38-c โ†’ misc-non-copyable-objects ๐Ÿงš โ€” periodt.
  • cert-flp30-c โ†’ bugprone-float-loop-counter ๐Ÿ’€ โ€” floats in a loop counter. we've all been there. (we have not, actually, and that's the point.)
  • cert-flp37-c โ†’ bugprone-suspicious-memory-comparison ๐ŸŒˆ โ€” another repeat appearance, love that for her.
  • cert-int09-c โ†’ readability-enum-initial-value โ€” and this row's third column literally says Yes ๐Ÿ’– โ€” that Yes is in the original table, bestie, we're just carrying it along verbatim, no editorialising.
  • cert-mem57-cpp โ†’ bugprone-default-operator-new-on-overaligned-type ๐Ÿ”ฅ โ€” say it with your chest.
  • cert-msc24-c โ†’ bugprone-unsafe-functions ๐Ÿ™ˆ โ€” the name says everything, honestly.
  • cert-msc30-c โ†’ misc-predictable-rand ๐Ÿ“ โ€” predictable rand, the classic.
  • cert-msc32-c โ†’ bugprone-random-generator-seed ๐ŸŒธ โ€” and the seed check to round the pair out.

lowkey this whole table is just "here's the CERT rule, here's the clang-tidy check that matches it," and we're going through every single row because that's the vibe โ€” nothing skipped, nothing merged, every name copied character for character so your config stays accurate ๐Ÿ’œ

  • cert-msc33-c โ†’ bugprone-unsafe-functions ๐Ÿซถ the CERT girlies said "don't call the sketchy functions bestie" and clang-tidy said "say less"
  • cert-msc50-cpp โ†’ misc-predictable-rand ๐Ÿ’€ if ur "random" is literally just vibes and a fixed seed, this one's gonna read u to filth
  • cert-msc51-cpp โ†’ bugprone-random-generator-seed ๐ŸŽ€ seeding drama!! this check is the friend who's like "bestie why is ur seed literally a constant"
  • cert-msc54-cpp โ†’ bugprone-signal-handler โœจ signal handler shenanigans, flagged and slayed
  • cert-oop11-cpp โ†’ performance-move-constructor-init ๐Ÿ”ฅ move constructor init but make it a performance issue, iconic behavior honestly
  • cert-oop54-cpp โ†’ bugprone-unhandled-self-assignment ๐Ÿ˜ญ the classic a = a moment. we've all been there. this check has NOT forgotten.
  • cert-oop57-cpp โ†’ bugprone-raw-memory-call-on-non-trivial-type ๐Ÿ’… raw memory calls on non-trivial types?? in THIS economy?? flagged.
  • cert-oop58-cpp โ†’ bugprone-copy-constructor-mutates-argument ๐Ÿ™ˆ ur copy constructor is out here MUTATING its argument, that's giving main character energy in the worst way
  • cert-pos44-c โ†’ bugprone-bad-signal-to-kill-thread โญ bad signal to kill a thread, no cap, stop doing that
  • cert-pos47-c โ†’ concurrency-thread-canceltype-asynchronous ๐Ÿฆ‹ asynchronous cancel type concurrency tea, piping hot
  • cert-sig30-c โ†’ bugprone-signal-handler ๐ŸŒธ same handler check, different CERT rule, we love a crossover moment
  • cert-str34-c โ†’ bugprone-signed-char-misuse ๐Ÿ’œ signed char misuse?? the way chars can just be signed or not is genuinely unhinged and this check knows it

ok and now we're sliding into the clang-analyzer section, where the mapping is basically "the check name you type" โ†’ "the human-readable Clang Static Analyzer name" ๐Ÿ‘‘

  • clang-analyzer-core.BitwiseShift โ†’ Clang Static Analyzer core.BitwiseShift ๐Ÿงš bitwise shifting, analyzed, gorgeous
  • clang-analyzer-core.CallAndMessage โ†’ Clang Static Analyzer core.CallAndMessage โœจ calls and messages, checked by the analyzer, periodt
  • clang-analyzer-core.DivideZero โ†’ Clang Static Analyzer core.DivideZero ๐Ÿ’€ dividing by zero, the timeless classic, caught in 4K
  • clang-analyzer-core.NonNullParamChecker โ†’ Clang Static Analyzer core.NonNullParamChecker ๐ŸŽ€ non-null param checking, lowkey essential
  • clang-analyzer-core.NullDereference โ†’ Clang Static Analyzer core.NullDereference ๐Ÿ˜ญ the null deref check, we've ALL met her
  • clang-analyzer-core.StackAddressEscape โ†’ Clang Static Analyzer core.StackAddressEscape ๐Ÿ”ฅ stack address escaping the function like it's moving out, flagged immediately
  • clang-analyzer-core.UndefinedBinaryOperatorResult โ†’ Clang Static Analyzer core.UndefinedBinaryOperatorResult ๐Ÿ’… undefined binary operator result โ€” note that the fully-spelled-out name runs kinda long, so it wraps onto its own lines in the original listing: Clang Static Analyzer up top and core.UndefinedBinaryOperatorResult underneath. same check tho, don't let the line break gaslight u ๐Ÿซถ

okay bestie, the checker list keeps going and we're still matching names to their analyzer entries โ€” same deal as before, left side is the checker flag, right side is what it calls itself. keep scrolling ๐Ÿ’…

  • clang-analyzer-core.VLASize โ€” Clang Static Analyzer core.VLASize โœจ
  • clang-analyzer-core.uninitialized.ArraySubscript โ€” Clang Static Analyzer core.uninitialized.ArraySubscript
  • clang-analyzer-core.uninitialized.Assign โ€” Clang Static Analyzer core.uninitialized.Assign
  • clang-analyzer-core.uninitialized.Branch โ€” Clang Static Analyzer core.uninitialized.Branch
  • clang-analyzer-core.uninitialized.CapturedBlockVariable โ€” Clang Static Analyzer core.uninitialized.CapturedBlockVariable ๐Ÿซถ
  • clang-analyzer-core.uninitialized.NewArraySize โ€” Clang Static Analyzer core.uninitialized.NewArraySize
  • clang-analyzer-core.uninitialized.UndefReturn โ€” Clang Static Analyzer core.uninitialized.UndefReturn
  • clang-analyzer-cplusplus.ArrayDelete โ€” Clang Static Analyzer cplusplus.ArrayDelete ๐Ÿฆ‹
  • clang-analyzer-cplusplus.InnerPointer โ€” Clang Static Analyzer cplusplus.InnerPointer
  • clang-analyzer-cplusplus.Move โ€” Clang Static Analyzer cplusplus.Move
  • clang-analyzer-cplusplus.NewDelete โ€” Clang Static Analyzer cplusplus.NewDelete
  • clang-analyzer-cplusplus.NewDeleteLeaks โ€” Clang Static Analyzer cplusplus.NewDeleteLeaks ๐Ÿ’–
  • clang-analyzer-cplusplus.PlacementNew โ€” Clang Static Analyzer cplusplus.PlacementNew
  • clang-analyzer-cplusplus.SelfAssignment โ€” Clang Static Analyzer cplusplus.SelfAssignment
  • clang-analyzer-cplusplus.StringChecker โ€” Clang Static Analyzer cplusplus.StringChecker ๐ŸŽ€
  • clang-analyzer-deadcode.DeadStores โ€” Clang Static Analyzer deadcode.DeadStores
  • clang-analyzer-fuchsia.HandleChecker โ€” Clang Static Analyzer fuchsia.HandleChecker ๐ŸŒธ
  • clang-analyzer-nullability.NullPassedToNonnull โ€” Clang Static Analyzer nullability.NullPassedToNonnull

the way each one just echoes its own checker name back at youโ€ฆ it's giving "I contain multitudes but the multitudes is just me" ๐Ÿ˜ญ anyway, more coming, don't close the terminal ๐Ÿ’œ

  • clang-analyzer-nullability.NullReturnedFromNonnull โ€” Clang Static Analyzer, short name nullability.NullReturnedFromNonnull ๐ŸŽ€
  • clang-analyzer-nullability.NullableDereferenced โ€” Clang Static Analyzer, short name nullability.NullableDereferenced ๐ŸŽ€
  • clang-analyzer-nullability.NullablePassedToNonnull โ€” Clang Static Analyzer, short name nullability.NullablePassedToNonnull ๐ŸŽ€
  • clang-analyzer-nullability.NullableReturnedFromNonnull โ€” Clang Static Analyzer, short name nullability.NullableReturnedFromNonnull ๐ŸŽ€

okay that's the whole nullability friend group, they literally all show up together, no cap ๐Ÿ’… every single one of them is a checker from the Clang Static Analyzer, and each one's short name is just the checker name minus the clang-analyzer- prefix โ€” iykyk.


  • clang-analyzer-optin.core.EnumCastOutOfRange โ€” Clang Static Analyzer, short name optin.core.EnumCastOutOfRange ๐Ÿ”ฅ
  • clang-analyzer-optin.cplusplus.UninitializedObject โ€” Clang Static Analyzer, short name optin.cplusplus.UninitializedObject ๐Ÿ”ฅ
  • clang-analyzer-optin.cplusplus.VirtualCall โ€” Clang Static Analyzer, short name optin.cplusplus.VirtualCall ๐Ÿ”ฅ
  • clang-analyzer-optin.mpi.MPI-Checker โ€” Clang Static Analyzer, short name optin.mpi.MPI-Checker ๐Ÿ”ฅ

these ones live under the optin umbrella bestie, which is giving "you gotta invite me first" energy ๐ŸŒธ โ€” note that optin.mpi.MPI-Checker has a literal hyphen in the last chunk, MPI-Checker, so copy it exactly or it will not vibe check pass ๐Ÿ˜ญ


  • clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker โ€” Clang Static Analyzer, short name optin.osx.cocoa.localizability.EmptyLocalizationContextChecker ๐Ÿ“
  • clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker โ€” Clang Static Analyzer, short name optin.osx.cocoa.localizability.NonLocalizedStringChecker ๐Ÿ“

the way these two have the longest names in the entire roster and I am obsessed โ€” optin.osx.cocoa.localizability.*, a whole little address, main character energy fr fr ๐Ÿ‘‘


  • clang-analyzer-optin.performance.GCDAntipattern โ€” Clang Static Analyzer, short name optin.performance.GCDAntipattern โญ
  • clang-analyzer-optin.performance.Padding โ€” Clang Static Analyzer, short name optin.performance.Padding โญ
  • clang-analyzer-optin.portability.UnixAPI โ€” Clang Static Analyzer, short name optin.portability.UnixAPI โญ
  • clang-analyzer-optin.taint.TaintedAlloc โ€” Clang Static Analyzer, short name optin.taint.TaintedAlloc โญ

optin.performance.*, optin.portability.*, optin.taint.* โ€” three different little sub-neighborhoods, all still repping Clang Static Analyzer and all still prefixed with clang-analyzer- in their long form ๐Ÿฆ‹


  • clang-analyzer-osx.API โ€” Clang Static Analyzer, short name osx.API ๐ŸŒˆ
  • clang-analyzer-osx.NumberObjectConversion โ€” Clang Static Analyzer, short name osx.NumberObjectConversion ๐ŸŒˆ
  • clang-analyzer-osx.ObjCProperty โ€” Clang Static Analyzer, short name osx.ObjCProperty ๐ŸŒˆ
  • clang-analyzer-osx.SecKeychainAPI โ€” Clang Static Analyzer, short name osx.SecKeychainAPI ๐ŸŒˆ

and these are the osx.* girlies โ€” short names so short and clean, just osx. plus the one word, we love a minimal aesthetic ๐ŸŽ€ every last one of them is a Clang Static Analyzer checker, and the short name is always the long name with the clang-analyzer- stripped off. that's the pattern, that's the lore, that's the whole thing ๐Ÿ’–

okay bestie we are deep in the checker trenches now โ€” this stretch of the page is literally just a two-column situation where every single line pairs a checker name on the left with its display title on the right, and the display title is always the same shape: Clang Static Analyzer glued onto the checker name itself. no cap. every line. the divider โ”€โ”€โ”€โ”€โ”€โ€ฆ repeating between them like a little drumroll ๐Ÿฅ

so like, every entry below is formatted name โ‡ข Clang Static Analyzer name, and bestie, yes, the right side is literally the left side with a prefix. that's the whole bit. slay, honestly. ๐Ÿ’…


  • clang-analyzer-osx.cocoa.AtSync โ€” display title: Clang Static Analyzer osx.cocoa.AtSync โœจ
  • clang-analyzer-osx.cocoa.AutoreleaseWrite โ€” display title: Clang Static Analyzer osx.cocoa.AutoreleaseWrite ๐ŸŽ€
  • clang-analyzer-osx.cocoa.ClassRelease โ€” display title: Clang Static Analyzer osx.cocoa.ClassRelease ๐Ÿฆ‹
  • clang-analyzer-osx.cocoa.Dealloc โ€” display title: Clang Static Analyzer osx.cocoa.Dealloc ๐ŸŒธ
  • clang-analyzer-osx.cocoa.IncompatibleMethodTypes โ€” display title: Clang Static Analyzer osx.cocoa.IncompatibleMethodTypes ๐Ÿ’…
  • clang-analyzer-osx.cocoa.Loops โ€” display title: Clang Static Analyzer osx.cocoa.Loops ๐Ÿซถ
  • clang-analyzer-osx.cocoa.MissingSuperCall โ€” display title: Clang Static Analyzer osx.cocoa.MissingSuperCall ๐Ÿ˜ญ
  • clang-analyzer-osx.cocoa.NSAutoreleasePool โ€” display title: Clang Static Analyzer osx.cocoa.NSAutoreleasePool ๐Ÿ”ฅ
  • clang-analyzer-osx.cocoa.NSError โ€” display title: Clang Static Analyzer osx.cocoa.NSError ๐Ÿ‘‘
  • clang-analyzer-osx.cocoa.NilArg โ€” display title: Clang Static Analyzer osx.cocoa.NilArg ๐ŸŒˆ
  • clang-analyzer-osx.cocoa.NonNilReturnValue โ€” display title: Clang Static Analyzer osx.cocoa.NonNilReturnValue โญ
  • clang-analyzer-osx.cocoa.ObjCGenerics โ€” display title: Clang Static Analyzer osx.cocoa.ObjCGenerics ๐Ÿ’€
  • clang-analyzer-osx.cocoa.RetainCount โ€” display title: Clang Static Analyzer osx.cocoa.RetainCount ๐Ÿ™ˆ
  • clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak โ€” display title: Clang Static Analyzer osx.cocoa.RunLoopAutoreleaseLeak ๐Ÿ“
  • clang-analyzer-osx.cocoa.SelfInit โ€” display title: Clang Static Analyzer osx.cocoa.SelfInit ๐Ÿงš
  • clang-analyzer-osx.cocoa.SuperDealloc โ€” display title: Clang Static Analyzer osx.cocoa.SuperDealloc ๐Ÿ’œ
  • clang-analyzer-osx.cocoa.UnusedIvars โ€” display title: Clang Static Analyzer osx.cocoa.UnusedIvars ๐Ÿ’–
  • clang-analyzer-osx.cocoa.VariadicMethodTypes โ€” display title: Clang Static Analyzer osx.cocoa.VariadicMethodTypes ๐ŸŽ€
  • clang-analyzer-osx.coreFoundation.CFError โ€” display title: Clang Static Analyzer osx.coreFoundation.CFError โœจ

not me noticing we just slid out of the osx.cocoa.* neighborhood and into osx.coreFoundation.* โ€” a whole new namespace, a whole new vibe ๐Ÿ’ซ and that last one, clang-analyzer-osx.coreFoundation.CFError, is giving "the page is about to change the subject" energy, iykyk.

if you're the copy-paste type (same, honestly ๐Ÿซถ), here's the raw name column in one place so you don't gotta hand-copy each one:

clang-analyzer-osx.cocoa.AtSync
clang-analyzer-osx.cocoa.AutoreleaseWrite
clang-analyzer-osx.cocoa.ClassRelease
clang-analyzer-osx.cocoa.Dealloc
clang-analyzer-osx.cocoa.IncompatibleMethodTypes
clang-analyzer-osx.cocoa.Loops
clang-analyzer-osx.cocoa.MissingSuperCall
clang-analyzer-osx.cocoa.NSAutoreleasePool
clang-analyzer-osx.cocoa.NSError
clang-analyzer-osx.cocoa.NilArg
clang-analyzer-osx.cocoa.NonNilReturnValue
clang-analyzer-osx.cocoa.ObjCGenerics
clang-analyzer-osx.cocoa.RetainCount
clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak
clang-analyzer-osx.cocoa.SelfInit
clang-analyzer-osx.cocoa.SuperDealloc
clang-analyzer-osx.cocoa.UnusedIvars
clang-analyzer-osx.cocoa.VariadicMethodTypes
clang-analyzer-osx.coreFoundation.CFError
bestie reminder, gently: this part of the page is just the name/title pairs โ€” it does not give you a description of what any of these check for, so don't go assuming. copy the names exactly, character for character, and don't be out here "fixing" the capitalization. AtSync is AtSync. periodt. ๐ŸŽ€

and likeโ€ฆ the dividers keep coming, the list keeps going, and we are absolutely not done yet โ€” next up the page keeps rolling and we keep reading. ๐Ÿ’œ

  • clang-analyzer-osx.coreFoundation.CFNumber โ€” bestie, this one's the Clang Static Analyzer checker for osx.coreFoundation.CFNumber ๐Ÿ’… it's giving "did you actually make that CFNumber right" energy, and it WILL clock you if you didn't ๐ŸŽ€
  • clang-analyzer-osx.coreFoundation.CFRetainRelease โ€” the Clang Static Analyzer checker for osx.coreFoundation.CFRetainRelease โœจ literally the one that side-eyes your retain/release balance like a group chat reading receipts ๐Ÿซถ no cap, memory management core.
  • clang-analyzer-osx.coreFoundation.containers.OutOfBounds โ€” Clang Static Analyzer, osx.coreFoundation.containers.OutOfBounds ๐Ÿฆ‹ she's the one who catches you reaching past the end of the container, and honestly? embarrassing for you, iconic for her ๐Ÿ’€
  • clang-analyzer-osx.coreFoundation.containers.PointerSizedValues โ€” Clang Static Analyzer, osx.coreFoundation.containers.PointerSizedValues ๐ŸŒธ the checker that's obsessed with whether your values are actually pointer-sized, because vibes are not sizes, bestie ๐Ÿ˜ญ
  • clang-analyzer-security.FloatLoopCounter โ€” Clang Static Analyzer, security.FloatLoopCounter โญ floating-point loop counters? in THIS economy? she flags it and she's right, fr fr ๐Ÿ”ฅ
  • clang-analyzer-security.PutenvStackArray โ€” Clang Static Analyzer, security.PutenvStackArray ๐Ÿ’– the one that clocks you handing putenv a stack array, which is giving "living dangerously" and not in the cute way ๐Ÿ™ˆ
  • clang-analyzer-security.SetgidSetuidOrder โ€” Clang Static Analyzer, security.SetgidSetuidOrder ๐Ÿ‘‘ order matters, bestie โ€” this checker's whole personality is making sure your setgid/setuid calls happen in the right sequence, periodt ๐Ÿ’œ
  • clang-analyzer-security.cert.env.InvalidPtr โ€” Clang Static Analyzer, security.cert.env.InvalidPtr ๐ŸŒˆ the cert/security-coding checker that catches invalid pointers in that whole cert.env situation, and she is NOT subtle about it ๐Ÿงš
  • clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling โ€” Clang Static Analyzer, security.insecureAPI.DeprecatedOrUnsafeBufferHandling ๐Ÿ”ฅ the long-name legend herself โ€” deprecated OR unsafe buffer handling, she's got a whole list and she's not afraid to use it ๐Ÿ’…
  • clang-analyzer-security.insecureAPI.UncheckedReturn โ€” Clang Static Analyzer, security.insecureAPI.UncheckedReturn ๐Ÿ˜ญ you ignored the return value?? she noticed. she ALWAYS notices ๐Ÿ“
  • clang-analyzer-security.insecureAPI.bcmp โ€” Clang Static Analyzer, security.insecureAPI.bcmp โœจ the insecure-API checker for bcmp, keeping tabs so you don't do something goofy with it ๐ŸŽ€
  • clang-analyzer-security.insecureAPI.bcopy โ€” Clang Static Analyzer, security.insecureAPI.bcopy ๐Ÿฆ‹ same deal, but bcopy โ€” she's watching, bestie, and the vibes are "please use something safer" ๐Ÿ’€
  • clang-analyzer-security.insecureAPI.bzero โ€” Clang Static Analyzer, security.insecureAPI.bzero ๐ŸŒธ the bzero checker, out here doing her little security sweep like the main character she is ๐Ÿ‘‘
  • clang-analyzer-security.insecureAPI.decodeValueOfObjCType โ€” Clang Static Analyzer, security.insecureAPI.decodeValueOfObjCType ๐Ÿ’– yep, that whole decodeValueOfObjCType situation โ€” she's got opinions and honestly they're valid ๐Ÿ™ˆ
  • clang-analyzer-security.insecureAPI.getpw โ€” Clang Static Analyzer, security.insecureAPI.getpw โญ the getpw insecure-API checker, showing up to say "bestie... maybe not" ๐ŸŒˆ
  • clang-analyzer-security.insecureAPI.gets โ€” Clang Static Analyzer, security.insecureAPI.gets ๐Ÿงš oh, gets?? gets?? she is SCREAMING, this API is a whole hazard and she's putting it on blast ๐Ÿ’€๐Ÿ”ฅ
  • clang-analyzer-security.insecureAPI.mkstemp โ€” Clang Static Analyzer, security.insecureAPI.mkstemp ๐Ÿ’œ the mkstemp checker, doing her little security vibe check on your temp-file game ๐ŸŽ€
  • clang-analyzer-security.insecureAPI.mktemp โ€” Clang Static Analyzer, security.insecureAPI.mktemp ๐ŸŒธ and mktemp too โ€” she's got the whole insecure-API roster memorized and she's not letting a single one slide, periodt โœจ

okay so this next batch is basically the whole clang-analyzer roster plus a couple of glow-ups at the end, and the format is honestly so simple it's giving flashcards: left side = the name, right side = what it lines up with. no cap, that's the whole vibe check. ๐Ÿ’…

for the analyzer ones, the right-hand column is literally the same checker wearing its Clang Static Analyzer name tag โ€” so don't panic when it looks like the same string twice. it's the same string twice. ๐Ÿ”ฅ

  • clang-analyzer-security.insecureAPI.rand โ†’ Clang Static Analyzer security.insecureAPI.rand
  • clang-analyzer-security.insecureAPI.strcpy โ†’ Clang Static Analyzer security.insecureAPI.strcpy
  • clang-analyzer-security.insecureAPI.vfork โ†’ Clang Static Analyzer security.insecureAPI.vfork
  • clang-analyzer-unix.API โ†’ Clang Static Analyzer unix.API
  • clang-analyzer-unix.BlockInCriticalSection โ†’ Clang Static Analyzer unix.BlockInCriticalSection
  • clang-analyzer-unix.Errno โ†’ Clang Static Analyzer unix.Errno
  • clang-analyzer-unix.Malloc โ†’ Clang Static Analyzer unix.Malloc
  • clang-analyzer-unix.MallocSizeof โ†’ Clang Static Analyzer unix.MallocSizeof
  • clang-analyzer-unix.MismatchedDeallocator โ†’ Clang Static Analyzer unix.MismatchedDeallocator
  • clang-analyzer-unix.StdCLibraryFunctions โ†’ Clang Static Analyzer unix.StdCLibraryFunctions
  • clang-analyzer-unix.Stream โ†’ Clang Static Analyzer unix.Stream
  • clang-analyzer-unix.Vfork โ†’ Clang Static Analyzer unix.Vfork
  • clang-analyzer-unix.cstring.BadSizeArg โ†’ Clang Static Analyzer unix.cstring.BadSizeArg
  • clang-analyzer-unix.cstring.NullArg โ†’ Clang Static Analyzer unix.cstring.NullArg
  • clang-analyzer-webkit.NoUncountedMemberChecker โ†’ Clang Static Analyzer webkit.NoUncountedMemberChecker
  • clang-analyzer-webkit.RefCntblBaseVirtualDtor โ†’ Clang Static Analyzer webkit.RefCntblBaseVirtualDtor
  • clang-analyzer-webkit.UncountedLambdaCapturesChecker โ†’ Clang Static Analyzer webkit.UncountedLambdaCapturesChecker

and then the last two are the plot twist ๐Ÿซถ because the right-hand column stops saying Clang Static Analyzer and is just another checker name instead. so these two are mapping one checker family onto a totally different one:

  • cppcoreguidelines-avoid-c-arrays โ†’ modernize-avoid-c-arrays
  • cppcoreguidelines-avoid-magic-numbers โ†’ readability-magic-numbers

like look at that second one โ€” cppcoreguidelines over here pointing at readability. different family, same energy, iconic honestly. ๐Ÿ˜ญ

bestie reminder: copy these character for character โ€” the dots, the capitals, the InsecureAPI vs insecureAPI casing, all of it. one wrong letter and you're debugging your config instead of your code. ๐Ÿ’€

also not me noticing how many of these are lowercase unix.something and then unix.cstring.something nested even deeper like a little matryoshka of checkers ๐ŸŽ€ โ€” keep the nesting exactly as written, don't flatten it, don't "tidy" it up. she's perfect as she is.

and we're still rolling โ€” plenty more of these to go. โœจ

  • cppcoreguidelines-c-copy-assignment-signature โ†’ misc-unconventional-assign-operator
  • cppcoreguidelines-explicit-constructor โ†’ misc-explicit-constructor โ€” third column: Yes
  • cppcoreguidelines-explicit-virtual-functions โ†’ modernize-use-override โ€” third column: Yes
  • cppcoreguidelines-macro-to-enum โ†’ modernize-macro-to-enum โ€” third column: Yes

okay this next one is so real, narrowing conversions are literally the villain of every codebase ๐Ÿ’€ no Yes in the third column either, so jot that down:

  • cppcoreguidelines-narrowing-conversions โ†’ bugprone-narrowing-conversions
  • cppcoreguidelines-noexcept-destructor โ†’ performance-noexcept-destructor โ€” third column: Yes
  • cppcoreguidelines-noexcept-move-operations โ†’ performance-noexcept-move-constructor โ€” third column: Yes
  • cppcoreguidelines-noexcept-swap โ†’ performance-noexcept-swap โ€” third column: Yes
  • cppcoreguidelines-non-private-member-variables-in-classes โ†’ misc-non-private-member-variables-in-classes
  • cppcoreguidelines-use-default-member-init โ†’ modernize-use-default-member-init โ€” third column: Yes

bestie we're moving into the fuchsia era now ๐Ÿฆ‹ two of them, both with a blank third column, no Yes in sight:

  • fuchsia-header-anon-namespaces โ†’ misc-anonymous-namespace-in-header
  • fuchsia-multiple-inheritance โ†’ misc-multiple-inheritance

and then the google crew rolls in ๐Ÿ‘‘ โ€” heads up, google-build-namespaces lowkey points at the exact same replacement as fuchsia-header-anon-namespaces above, misc-anonymous-namespace-in-header. not me noticing that ๐Ÿ’…

  • google-build-namespaces โ†’ misc-anonymous-namespace-in-header
  • google-explicit-constructor โ†’ misc-explicit-constructor โ€” third column: Yes
  • google-readability-braces-around-statements โ†’ readability-braces-around-statements โ€” third column: Yes
  • google-readability-casting โ†’ modernize-avoid-c-style-cast โ€” third column: Yes
  • google-readability-function-size โ†’ readability-function-size
  • google-readability-namespace-comments โ†’ llvm-namespace-comment
  • llvm-else-after-return โ†’ readability-else-after-return โ€” third column: Yes

so yeah, if you were still out here typing the old names, this is your sign to swap them ๐Ÿซถ the Yes ones have that extra little something in the third column, the rest are just a clean one-to-one glow-up โœจ

  • llvm-qualified-auto โ†’ readability-qualified-auto โ†’ Yes ๐ŸŒธ โ€” this row pairs the check on the left with readability-qualified-auto in the middle, and the last column literally just says Yes. That's it, that's the row, bestie ๐Ÿ’…
  • performance-faster-string-find โ†’ performance-prefer-single-char-overloads โ†’ Yes ๐Ÿ”ฅ โ€” same deal: performance-faster-string-find on the left, performance-prefer-single-char-overloads in the middle, and a big sparkly Yes closing it out โœจ

And then the line does its little divider thing again:

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

lowkey obsessed with how these rows are just out here being perfectly formatted ๐ŸŽ€ every name copied exact, nothing dropped, vibe intact ๐Ÿ’œ

๐Ÿ‘‘ Query Based Custom Clang-Tidy Checks

๐ŸŒธ Introduction

Okay bestie, so this page is literally a whole tour of how to add query based custom checks for clang-tidy โœจ. Think of it as a little teaching era โ€” examples, not vibes-only handwaving.

So the tea is: custom checks are based on clang-query syntax. Every single custom check gets registered in a custom module to avoid name conflict โ€” no stepping on anyone's toes, periodt ๐Ÿ’…. And you can turn them on or off with the checks option, exactly like the built-in checks do. Same energy, same rules.

Also โ€” and this is the cute part โ€” custom checks support inheritance from parent configurations like other configuration items do ๐Ÿซถ. Family tree behavior, iykyk.

The goals of this whole thing are, and I quote the vibe:

  • easy to write
  • cross platform
  • multiple versions supported
  • a toolkit for custom clang-tidy rules

And the Non-Goals, because not everything is the main character:

  • complex checks
  • performance
  • fix-its
  • etc.

So like, know what it's for and know what it's not for, bestie ๐Ÿ’–.


๐ŸŽ€ Configuration

CustomChecks is a list of custom checks. Each check must contain these three things, no skipping:

  • Name โ€” the check name, and you can use it in the -checks option ๐Ÿ”ฅ
  • Query โ€” a clang-query string
  • Diagnostic โ€” the list of diagnostics to be reported, and it's giving sub-fields:
    • BindName โ€” name of the node to be bound in Query
    • Message โ€” message to be reported
    • Level โ€” severity of the diagnostic; the possible values are Note and Warning โญ

CustomChecks can be configured by the Checks option in the configuration file ๐ŸŒˆ.

โœจ Example

Checks: -*,custom-call-main-function
CustomChecks:
  - Name: call-main-function
    Query: |
        match callExpr(
          callee(
            functionDecl(isMain()).bind("fn")
          )
        ).bind("callee")
    Diagnostic:
      - BindName: fn
        Message: main function.
        Level: Note
      - BindName: callee
        Message: call to main function.
        Level: Warning

And here's what it does out in the wild ๐ŸŒธ:

int main(); // note: main function.

void bar() {
  main(); // warning: call to main function. [custom-call-main-function]
}

Like โ€” look at it go ๐Ÿ’…. The note fires on the declaration, the warning fires on the call site, and the check name shows up in brackets like a little name tag ๐ŸŽ€.

๐Ÿ’€ Matters Need Attention

Real talk, bestie โ€” this feature needs to be explicitly enabled with --experimental-custom-checks because it is currently in the experimental stage ๐Ÿ™ˆ. And you're welcome to submit any suggestions in the link.

During the experimental stage, the required configuration structure of this feature may be changed in the future. Future changes will be as forward-compatible as possible, but this is not a guarantee ๐Ÿ˜ญ. Do not come for me if your config needs a glow-up.

And that's not even the end of it โ€” in subsequent versions, including non-experimental stage, the query statements will change at any time. Because the essence of clang-query is to

...it parses the query string and dynamically generates the matching AST matcher on the fly ๐Ÿ’ซ. So like, its whole entire personality is dependent on the functions the AST matcher library hands over โ€” that's it, that's the vibe. The ast matcher shifts around whenever the clang AST changes, so bestie, please go check the ast matcher reference. It's giving "read the docs or perish" fr fr ๐Ÿ“š.


โœจ Clang-tidy IDE/Editor Integrations โœจ

Okay so clang-tidy isn't just a lil standalone tool you run in your terminal like a lone wolf ๐Ÿบ โ€” it's also integrated into a whole bunch of IDEs, code analyzers, and editors. And the recommendation? Use clangd ๐Ÿ’š โ€” it integrates clang-tidy and is available in most major editors through plugins (Vim, Emacs, Visual Studio Code, Sublime Text and more). Slay.

The following is the most well-known clang-tidy integrations, in detail โ€” features: on-the-fly inspection, check list configuration (GUI), options to checks (GUI), configuration via .clang-tidy files, and custom clang-tidy binary. + means yes, - means no:

  • A.L.E. for Vim โ€” on-the-fly inspection: + ยท check list configuration (GUI): - ยท options to checks (GUI): - ยท configuration via .clang-tidy files: - ยท custom clang-tidy binary: +
  • Clang Power Tools for Visual Studio โ€” on-the-fly inspection: - ยท check list configuration (GUI): + ยท options to checks (GUI): - ยท configuration via .clang-tidy files: + ยท custom clang-tidy binary: -
  • Clangd โ€” on-the-fly inspection: + ยท check list configuration (GUI): - ยท options to checks (GUI): - ยท configuration via .clang-tidy files: + ยท custom clang-tidy binary: -
  • CLion IDE โ€” on-the-fly inspection: + ยท check list configuration (GUI): + ยท options to checks (GUI): + ยท configuration via .clang-tidy files: + ยท custom clang-tidy binary: +
  • CodeChecker โ€” on-the-fly inspection: - ยท check list configuration (GUI): - ยท options to checks (GUI): - ยท configuration via .clang-tidy files: - ยท custom clang-tidy binary: +
  • CPPCheck โ€” on-the-fly inspection: - ยท check list configuration (GUI): - ยท options to checks (GUI): - ยท configuration via .clang-tidy files: - ยท custom clang-tidy binary: -
  • CPPDepend โ€” on-the-fly inspection: - ยท check list configuration (GUI): - ยท options to checks (GUI): - ยท configuration via .clang-tidy files: - ยท custom clang-tidy binary: -
  • Flycheck for Emacs โ€” on-the-fly inspection: + ยท check list configuration (GUI): - ยท options to checks (GUI): - ยท configuration via .clang-tidy files: + ยท custom clang-tidy binary: +
  • KDevelop IDE โ€” on-the-fly inspection: - ยท check list configuration (GUI): + ยท options to checks (GUI): + ยท configuration via .clang-tidy files: + ยท custom clang-tidy binary: +
  • Qt Creator IDE โ€” on-the-fly inspection: + ยท check list configuration (GUI): + ยท options to checks (GUI): - ยท configuration via .clang-tidy files: + ยท custom clang-tidy binary: +
  • ReSharper C++ for Visual Studio โ€” on-the-fly inspection: + ยท check list configuration (GUI): + ยท options to checks (GUI): - ยท configuration via .clang-tidy files: + ยท custom clang-tidy binary: +
  • Syntastic for Vim โ€” on-the-fly inspection: + ยท check list configuration (GUI): - ยท options to checks (GUI): - ยท configuration via .clang-tidy files: - ยท custom clang-tidy binary: +
  • Visual Assist for Visual Studio โ€” on-the-fly inspection: + ยท check list configuration (GUI): + ยท options to checks (GUI): - ยท configuration via .clang-tidy files: - ยท custom clang-tidy binary: -
Not CLion and KDevelop out here absolutely ate that table, no cap ๐Ÿ‘‘ โ€” every single +. We love a full sweep ๐Ÿ’….

๐Ÿ–ฅ๏ธ IDEs

CLion 2017.2 and later integrates clang-tidy as an extension to the built-in code analyzer โœจ. And starting from 2018.2 EAP, CLion lets you use clang-tidy via Clangd ๐ŸŒฟ.

So what do you actually get? Inspections and the applicable quick-fixes are performed on the fly โšก, and checks can be configured in the standard command line format ๐Ÿ–ฅ๏ธ. In this integration you can:

  • switch to a clang-tidy binary that's different from the bundled one ๐Ÿ”„
  • pass the configuration in .clang-tidy files instead of using the IDE settings ๐Ÿ“„
  • configure options for particular checks ๐ŸŽ›๏ธ

It's giving "have it your way" and honestly? Obsessed ๐Ÿซถ.

Oh, and bestie โ€” clang-tidy is everywhere. Like, it's not just a terminal thing. Let's talk about where it lives ๐Ÿ’…

KDevelop 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 specified location and parses its output to give you a list of issues. Cute, right? โœจ

QtCreator 4.6 folds clang-tidy warnings right into the editor diagnostics under the Clang Code Model. To actually employ clang-tidy inspection in QtCreator, you need to make a copy of one of the presets and pick the checks you want performed. And since QtCreator 4.7, project-wide analysis is possible with the Clang Tools analyzer. Slay ๐ŸŽ€

MS Visual Studio can integrate clang-tidy by means of three different tools, no cap:

  • The ReSharper C++ extension, version 2017.3 and later, gives you seamless clang-tidy integration โ€” checks and quick-fixes run alongside native inspections. And apart from that? ReSharper C++ also incorporates clang-tidy as a separate step of its code clean-up process.
  • Visual Assist build 2210 includes a subset of the clang-tidy checklist so it can inspect the code as you edit.
  • Another way to bring clang-tidy functionality to Visual Studio is the Clang Power Tools plugin, which includes most of the clang-tidy checks and runs them during compilation or as a separate step of code analysis.

๐ŸŒธ Editors ๐ŸŒธ

Emacs24, when you expand it with the Flycheck plugin, incorporates the clang-tidy inspection into the syntax analyzer. For Vim, you can use Syntastic, which includes clang-tidy, or A.L.E., a lint engine that applies clang-tidy along with other linters. Vim girlies stay winning ๐Ÿซถ

๐Ÿฆ‹ Analyzers ๐Ÿฆ‹

clang-tidy is integrated in CPPDepend starting from version 2018.1 and CPPCheck 1.82. The CPPCheck integration lets you import Visual Studio solutions and run the clang-tidy inspection on them. And the CodeChecker application, version 5.3 or later โ€” which also comes as a plugin for Eclipse โ€” supports clang-tidy as a static analysis instrument and allows you to use a custom clang-tidy binary. Iconic. ๐Ÿ‘‘


โœจ Getting Involved โœจ

clang-tidy has several own checks and can run Clang static analyzer checks, but honestly? Its real power is in the ability to easily write custom checks. That's the main character energy right there ๐Ÿ’–

Checks are organized in modules, which can be linked into clang-tidy with minimal or no code changes in clang-tidy itself. Lowkey magical.

Checks can plug into the analysis on the preprocessor level using PPCallbacks or on the AST level using AST Matchers. When an error is found, checks can report them in a way similar to how Clang diagnostics work. And a fix-it hint can be attached to a diagnostic message โ€” yes bestie, automatic fixes ๐ŸŒˆ

The interface provided by clang-tidy makes it easy to write useful and precise checks in just a few lines of code. If you have an idea for a good check, the rest of this document explains how to do this. Let's gooo โญ

There are a few tools particularly useful when 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.
Also ๐Ÿ“Œ If CMake is configured with CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS=NO, clang-tidy will not be built with support for query based checks.

๐ŸŽ€ Choosing the Right Place for your Check ๐ŸŽ€

So you have an idea for a check. Before anything else, you gotta decide whether it should be implemented as a:

  • Clang diagnostic: if the check is generic enough, targets code patterns that most probably are bugs (rather than style or readability issues), can be implemented effectively and with extremely low false-positive rate, it may make a good Clang diagnostic.
  • Clang static analyzer check: if the check requires some sort of control flow analysis, it should probably be implemented as a static analyzer check.
  • clang-tidy check is a good choice for linter-style checks, checks that are related to a certain coding style, checks that address code readability, etc.

๐Ÿ’œ Preparing your Workspace ๐Ÿ’œ

New to LLVM development? Bestie, read the Getting Started with the LLVM System, Using Clang Tools and How To Setup Clang Tooling For LLVM documents to check out and build LLVM, Clang and Clang Extra Tools with CMake. It's giving homework but it's worth it ๐Ÿ“š

Once you are done, change to the llvm/clang-tools-extra directory, and let's start!

When you configure the CMake build, make sure that you enable the clang and clang-tools-extra projects to build clang-tidy. Because your new check will have associated documentation, you will also want to install Sphinx and enable it in the CMake configuration. And to save build time of the core Clang libraries, you may want to only enable the X86 target in the CMake configuration. Efficient queen behavior ๐Ÿ’…


๐ŸŒˆ The Directory Structure ๐ŸŒˆ

clang-tidy source code resides in the llvm/clang-tools-extra directory and is structured like this โ€” vibe check the layout:

clang-tidy/                       # Clang-tidy core.
|-- ClangTidy.h                   # Interfaces for users.
|-- ClangTidyCheck.h              # Interfaces for checks.
|-- ClangTidyModule.h             # Interface for clang-tidy 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
    ...

๐Ÿ’– Writing a clang-tidy Check ๐Ÿ’–

So you have an idea of a useful check for clang-tidy. Obsessed already โœจ

First, if you're not familiar with LLVM development, read through the Getting Started with the LLVM System document for instructions on setting up your workflow, and the LLVM Coding Standards document to familiarize yourself with the coding style used in the project. Code reviews for clang-tidy contributions are done through LLVM Github.

Next, you need to decide which module the check belongs to. Modules live 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. iykyk ๐Ÿ“Œ

After choosing the module and the name for the check, run the clang-tidy/add_new_check.py script to create the skeleton of the check and plug it into clang-tidy. It's the recommended way of adding new checks โ€” the way, honestly ๐Ÿซถ

By default, the new check will apply only to C++ code. If it should apply under different language options, use the --language script's parameter.

So if 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 look at the check class definition in more detail:

...

#include "../ClangTidyCheck.h"

namespace clang::tidy::readability {

...
class AwesomeFunctionNamesCheck : public ClangTidyCheck {
public:
  AwesomeFunctionNamesCheck(StringRef Name, ClangTidyContext *Context)
      : ClangTidyCheck(Name, Context) {}
  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
    return LangOpts.CPlusPlus;
  }
};

} // namespace clang::tidy::readability

...

The constructor of the check receives the Name and Context parameters, and must forward them to the ClangTidyCheck constructor. No skipping that step bestie ๐Ÿ™ˆ

In our case the check needs to operate on the AST level and it overrides the registerMatchers and check methods. If we wanted to analyze code on the preprocessor level, we'd need instead to override the registerPPCallbacks method.

In the registerMatchers method, we create an AST Matcher (see AST Matchers for more information) that will find the pattern in the AST that we want to inspect. The results of the matching are passed to the check method, which can further inspect them and report diagnostics.

using namespace ast_matchers;

void AwesomeFunctionNamesCheck::registerMatchers(MatchFinder *Finder) {
  Finder->addMatcher(functionDecl().bind("x"), this);
}

void AwesomeFunctionNamesCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
  if (!MatchedDecl->getIdentifier() || MatchedDecl->getName().startswith("awesome_"))
    return;
  diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome")
      << MatchedDecl
      << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
}

(If you want to see an example of a useful check, look at clang-tidy/google/ExplicitConstructorCheck.h and clang-tidy/google/ExplicitConstructorCheck.cpp.) ๐Ÿ‘€

If you need to interact with macros or preprocessor directives, you will want to override the method registerPPCallbacks. Word of warning 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, but you got this ๐Ÿ’ช


๐Ÿงš Check development tips ๐Ÿงš

Writing your first check can be a daunting task, particularly if you are unfamiliar with the LLVM and Clang code bases. Fr fr, it be like that sometimes ๐Ÿ˜ญ But here are some suggestions for orienting yourself in the codebase and working on your check incrementally โ€” go slow, stay cute, ship it ๐Ÿ“

Guide to useful documentation

So many of the support classes LLVM built are straight-up shared with Clang โ€” think StringRef and SmallVector. Those faves, plus a whole crew of other commonly used classes, get the full tour in the Important and useful LLVM APIs and Picking the Right Data Structure for the Task sections of the [LLVM Programmer's Manual](https://llvm.org/docs/ProgrammersManual.html). And bestie, you do not need to memorise every detail of these classes โ€” the generated doxygen documentation has literally everything whenever you need it. Also, in the header LLVM/ADT/STLExtras.h you'll find glow-up versions of the STL algorithms that operate on LLVM containers, like llvm::all_of. ๐Ÿซถ

Clang sits on top of LLVM and brings its own set of classes you'll be vibing with while you write your check. When a check issues diagnostics and fix-its, those get associated with locations in the source code. Source code locations, source files, ranges of source locations and the SourceManager class are the whole mechanism for describing those locations. All of that and more lives in the "Clang" CFE Internals Manual. And while the doxygen docs are the reference for Clang internals, this doc is the guide for actual developers. The topics in that manual that a check dev actually cares about are:

  • The Clang "Basic" Library โ€” everything about diagnostics, fix-it hints and source locations.
  • The Lexer and Preprocessor Library โ€” everything about tokens, lexing (that's turning characters into tokens, iykyk) and the preprocessor.
  • The AST Library โ€” how C++ source statements get represented as an abstract syntax tree (AST).

Most checks interact with C++ source code through the AST. Some checks interact with the preprocessor. The input source file gets lexed and preprocessed, then parsed into the AST. Once the AST is fully built, the check runs by applying the check's registered AST matchers against the AST and invoking the check with the set of matched nodes from that AST. Watching the preprocessor do its thing is detached from AST construction, but a check can still collect info during preprocessing for later use, when nodes get matched by the AST. ๐Ÿ’…

Every syntactic (and sometimes semantic) element of your C++ source code is represented by a different class in the AST. You pick the portions of the AST you're into by composing AST matcher functions. Definitely go study the AST Matcher Reference carefully so you actually understand how the different matcher functions relate to each other. ๐Ÿ‘‘


Using the Transformer library

The Transformer library lets you write a check that transforms source code by expressing the transformation as a RewriteRule. It gives you functions for composing edits to source code so you can build rewrite rules. Unless you genuinely need low-level source location manipulation, maybe consider writing your check with the Transformer library โ€” the Clang Transformer Tutorial describes it in detail.

To use the Transformer library, make these changes to the code the add_new_check.py script generates for you:

  • Include ../utils/TransformerClangTidyCheck.h instead of ../ClangTidyCheck.h.
  • Change your check's base class 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 that function in your check's constructor to pass the rewrite rule to TransformerClangTidyCheck's constructor.

Developing your check incrementally

The absolute best way to develop your check is to start with simple test cases and ramp up the complexity incrementally. The test file that add_new_check.py creates is your starting point. The rough outline of the process goes 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 check-clang-tools target to confirm the test passes.
  • Repeat until all aspects of your check are covered by tests. ๐Ÿ”

Fastest way to prototype your matcher? Use clang-query and build that matcher up interactively. For complicated matchers, build up a matching expression incrementally and use clang-query's let command to save named matching expressions, which keeps your matcher from becoming a vibe-crashing nightmare:

clang-query> let c1 cxxRecordDecl()
clang-query> match c1

Alternatively, pressing tab after a previous matcher's open parentheses also shows which matchers can be chained onto the previous one โ€” though some matchers that do work might not show up in the list. Just so you know: tab completion doesn't currently work on Windows. ๐Ÿ™ˆ

Same energy as breaking a huge function into smaller chunks with intention-revealing names so a complex algorithm actually makes sense โ€” breaking a matcher into smaller matchers with intention-revealing names makes a complicated matcher make sense too.

Once you've got a working clang-query matcher, the C++ API matchers will be the same or similar to the one you built interactively (there can be cases where they differ slightly). Use local variables to preserve those intention-revealing names you gave your nested matchers. โœจ


Creating private matchers

Sometimes you wanna match a specific aspect of the AST that the existing AST matchers just don't provide. You can create your own private matcher using the exact same infrastructure as the public ones. A private matcher can simplify the processing in your check method by eliminating complex hand-crafted AST traversal of the matched nodes. Using a private matcher lets you select exactly the portions of the AST you want, right there in the matcher, and then refer to it by a bound name in the check method. Chef's kiss. ๐ŸŽ€


Unit testing helper code

Private custom matchers are the poster child for auxiliary support code for your check that can be tested with a unit test. Testing your matchers or other support classes is genuinely easier with a unit test than with a FileCheck integration test. The ASTMatchersTests target contains unit tests for the public AST matcher classes and is a great source of testing idioms for matchers.

You can build the Clang-Tidy unit tests by building the ClangTidyTests target. Heads up: test targets in LLVM and Clang are excluded from the "build all" style action of IDE-based CMake generators, so you have to explicitly build the target for the unit tests to actually get built. ๐Ÿ’€


Making your check robust

Once your check is covered in the basic "happy path" scenarios, you'll wanna torture that check with as many edge cases as you can cover, so it comes out genuinely robust. Running your check on a large code base like Clang/LLVM is a great way to catch stuff you forgot to account for in your matchers. That said, the LLVM code base might not be enough for testing โ€” it was developed against a particular set of coding styles and quality measures. The bigger the corpus of code your check gets tested against, the more confidence the community has in its efficacy and its false-positive rate. ๐Ÿ”ฅ

Some suggestions to keep your check 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'll have to do this one manually until automated support for checking messages and fix-its gets 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. For example, when a fix-it inserts new lines, use the source file's existing newline style instead of hard-coding \n. You can use SourceManager::getBufferData(FileID).detectEOL() to get the newline style for a file.
  • Watch out for high false-positive rates. Ideally a check would have zero false positives, but matching against an AST isn't control- or data flow-sensitive, so a number of false positives are expected. The higher the false-positive rate, the less likely your check gets adopted in practice. Put mechanisms in place to help the user manage false positives.
  • There are two primary mechanisms for managing false positives: supporting a code pattern that lets the programmer silence the diagnostic in an ad hoc manner, and check configuration options to control the check's behavior.
  • Consider supporting a code pattern that lets the programmer silence the diagnostic whenever such a pattern can clearly express their intent. Example: allowing an explicit cast to void to silence an unused variable diagnostic.
  • Consider adding check configuration options so users can opt into more aggressive checking behavior without burdening everyone for the common high-confidence cases. ๐ŸŒธ

Documenting your check

The add_new_check.py script creates entries in the release notes, the list of checks, and a new file for the check documentation itself. Highly recommend: write a concise single-sentence summary of what your check does, and repeat that sentence in the release notes, as the first sentence in the doxygen comments in your check class's header file, and as the first sentence of the check documentation. Also โ€” avoid the phrase "this check" in your check summary and check documentation. It's giving filler, bestie. ๐Ÿงš

If your check relates to a published coding guideline (C++ Core Guidelines, SEI CERT, etc.) or a style guide, drop links to the relevant guideline or style guide sections in your check's documentation. ๐Ÿ”—

Provide enough examples of the diagnostics and fix-its your check provides that 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 โ€” that helps users understand the scope of the diagnostics and fix-its your check provides. ๐Ÿ’œ

Building the target docs-clang-tools-html will run the Sphinx documentation generator and create HTML documentation files in the tools/clang/tools/extra/docs/html directory in your build tree ๐ŸŒˆ. Make sure that your check is correctly shown in the release notes and the list of checks, bestie โ€” no ghosting your own check in the docs! And make sure the formatting and structure of your check's documentation look correct: there is no trailing whitespaces and lines are no longer than 80 characters โœจ.

To validate your files, please use doc8 as described below ๐Ÿซถ.

Clang-Tidy uses doc8 to check .rst files for formatting consistency. You can install doc8 with pip:

$ pip install doc8

To run doc8 on the modified documentations:

$ git diff --name-only HEAD -- clang-tools-extra/docs/clang-tidy/ | grep "\.rst$" | xargs -r doc8

๐ŸŽ€ Registering your Check ๐ŸŽ€

(The add_new_check.py script takes care of registering the check in an existing module. If you want to create a new module or know the details, read on. ๐Ÿ‘€)

The check should be registered in the corresponding module with a distinct name:

class MyModule : public ClangTidyModule {
 public:
  void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
    CheckFactories.registerCheck<ExplicitConstructorCheck>(
        "my-explicit-constructor");
  }
};

Now we need to register the module in the ClangTidyModuleRegistry using a statically initialized variable:

static ClangTidyModuleRegistry::Add<MyModule> X("my-module",
                                                "Adds my lint checks.");

When using LLVM build system, we need to use the following hack to ensure the module is linked into the clang-tidy binary ๐Ÿ’…:

Add this near the ClangTidyModuleRegistry::Add<MyModule> variable:

// This anchor is used to force the linker to link in the generated object file
// and thus register the MyModule.
volatile int MyModuleAnchorSource = 0;

And this to the main translation unit of the clang-tidy binary (or the binary you link the clang-tidy library in) clang-tidy/ClangTidyForceLinker.h:

// This anchor is used to force the linker to link the MyModule.
extern volatile int MyModuleAnchorSource;
static int MyModuleAnchorDestination = MyModuleAnchorSource;

๐Ÿ’œ Configuring Checks ๐Ÿ’œ

If a check needs configuration options, it can access check-specific options using the Options.get<Type>("SomeOption", DefaultValue) call in the check constructor. In this case, the check should also override the ClangTidyCheck::storeOptions method to make the options provided by the check discoverable ๐Ÿ”. This method lets clang-tidy know which options the check implements and what the current values are (e.g. for the -dump-config command-line option).

class MyCheck : public ClangTidyCheck {
  const unsigned SomeOption1;
  const std::string SomeOption2;

public:
  MyCheck(StringRef Name, ClangTidyContext *Context)
    : ClangTidyCheck(Name, Context),
      SomeOption1(Options.get("SomeOption1", -1U)),
      SomeOption2(Options.get("SomeOption2", "some default")) {}

  void storeOptions(ClangTidyOptions::OptionMap &Opts) override {
    Options.store(Opts, "SomeOption1", SomeOption1);
    Options.store(Opts, "SomeOption2", SomeOption2);
  }
  ...

Assuming the check is registered with the name "my-check", the option can then be set in a .clang-tidy file in the following way:

CheckOptions:
  my-check.SomeOption1: 123
  my-check.SomeOption2: 'some other value'

If you need to specify check options on a command line, you can use the inline YAML format:

$ clang-tidy -config="{CheckOptions: {a: b, x: y}}" ...

๐Ÿฆ‹ Testing Checks ๐Ÿฆ‹

To run tests for clang-tidy, build the check-clang-tools target. For instance, if you configured your CMake build with the ninja project generator, use the command:

$ ninja check-clang-tools

clang-tidy checks can be tested using either unit tests or lit 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 ๐Ÿงš.

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 with either CHECK-FIXES or CHECK-MESSAGES for clang-tidy tests.

An additional check enabled by check_clang_tidy.py ensures that if CHECK-MESSAGES: is used in a file then every warning or error must have an associated CHECK in that file. Or, you can use CHECK-NOTES: instead, if you want to also ensure that all the notes are checked ๐Ÿ‘‘

To use the check_clang_tidy.py script, put a .cpp file with the appropriate RUN line in the test/clang-tidy directory. Use CHECK-MESSAGES: and CHECK-FIXES: lines to write checks against diagnostic messages and fixed code.

It's advised to make the checks as specific as possible to avoid checks matching incorrect parts of the input. Use [[@LINE+X]]/[[@LINE-X]] substitutions and distinct function and variable names in the test code ๐Ÿ“.

Here's an example of a test using the check_clang_tidy.py script (the full source code is at test/clang-tidy/checkers/google/readability-casting.cpp):

// 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 ๐Ÿ’….

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;$

The -std flag controls which C or C++ standard(s) the test is compiled under. It accepts a comma-separated list of standards and supports an -or-later suffix โœจ:

  • -std=c++17: runs the test only with C++17.
  • -std=c++17-or-later: runs the test once for each standard from C++17 onwards (currently C++17, C++20, C++23, and C++26), in separate invocations. Use this when a check should work correctly on all modern standards.
  • -std=c++14,c++17: runs the test once with C++14 and once with C++17.

When no -std is given, check_clang_tidy.py defaults to c++11-or-later for C++ files and c99-or-later for C files. The check skeleton generated by add_new_check.py uses the -or-later form by default. Prefer -std=<minimum>-or-later unless the test expects behavior that only applies to a specific standard version ๐Ÿซถ.

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.

If you need multiple files to exercise all the aspects of your check, it is recommended you place them in a subdirectory named for the check under the Inputs directory for the module containing your check. This keeps the test directory from getting cluttered ๐ŸŒธ.

If you need to validate how your check interacts with system header files, a set of simulated system header files is located in the checkers/Inputs/Headers directory. The path to this directory is available in a lit test with the variable %clang_tidy_headers ๐ŸŽ€.


๐Ÿ”ฅ Submitting a Pull Request ๐Ÿ”ฅ

Before submitting a pull request, contributors are encouraged to run clang-tidy and clang-format on their changes to ensure code quality and catch potential issues. While clang-tidy is not currently enforced in CI, following this practice helps maintain code consistency and prevent common errors ๐Ÿ’–.

Here's a useful command to check your staged changes:

$ git diff --staged -U0 | ./clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \
    -j $(nproc) -path build/ -p1 -only-check-in-db
$ git clang-format
Note that some warnings may be false positives or require careful consideration before fixing. Use your judgment and feel free to discuss in the pull request if you're unsure about a particular warning ๐Ÿ™ˆ.

๐Ÿงš Out-of-tree check plugins ๐Ÿงš

okay so you wanna build a check that lives outside the clang-tidy tree? valid! ๐Ÿ’… honestly the steps are lowkey the same as the ones we just walked through โ€” make a new module, do the little hacks to register the module, all that. The one glow-up is this: the plugin is a shared library whose code lives outside the clang-tidy build system. That's it, that's the vibe.

Build and link this shared library against LLVM exactly like you would for any other kind of Clang plugin. If you're using CMake, bestie, use the keyword MODULE while invoking add_library or llvm_add_library โ€” that's the move, no cap. โœจ

Then you load it by passing -load to clang-tidy, plus the names of the checks you actually wanna enable:

$ clang-tidy --checks=-*,my-explicit-constructor -list-checks -load myplugin.so
โš ๏ธ Heads up: there are no expectations regarding ABI and API stability. None. Zero. So your plugin must be compiled against the version of clang-tidy that will be loading the plugin. Mismatch it and it's giving ๐Ÿ’€.

Plugins can use threads, TLS, or any other facilities available to in-tree code which is accessible from the external headers. Go off, honestly. ๐Ÿ”ฅ

Also ngl, testing out-of-tree checks might involve getting llvm-lit from an LLVM installation compiled from source. Peep Getting Started with the LLVM System for ways to do so. ๐Ÿซถ

Alternatively? Get lit following the test-suite guide, grab the FileCheck binary, and write a version of check_clang_tidy.py to suit your needs. DIY queen behaviour. ๐ŸŽ€


๐ŸŒˆ Running clang-tidy on LLVM ๐ŸŒˆ

To test a check, it's best to try it out on a larger code base โ€” and LLVM and Clang are the natural targets since you already have the source code around. Convenient af. ๐Ÿ˜Œ

The most convenient way to run clang-tidy is with a compile command database, and CMake can automatically generate one. 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. The script executes clang-tidy with the default set of checks on every translation unit in the compile command database and displays the resulting warnings and errors. And she's got options โ€” the script provides 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. slay. ๐Ÿ’…

๐Ÿ’œ On checks profiling ๐Ÿ’œ

clang-tidy can collect per-check profiling info and output it for each processed source file (translation unit). Numbers?? for me?? ๐Ÿ‘‘

To enable profiling info collection, use the -enable-check-profile argument. The timings will be output to stderr as a table. Example output, look at her:

$ 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. honestly, the CMakeLists alone is a whole saga. ๐Ÿ˜ญ

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. (yes, actual code, not just vibes ๐Ÿ’€)

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. iykyk. ๐Ÿงš

๐ŸŒธ List of projects and tools ๐ŸŒธ

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. periodt. ๐Ÿ‘‘


โญ 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. honestly iconic behaviour. ๐Ÿ’…

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. go peep the whole menu bestie ๐Ÿ“

okay bestie, buckle up because this is the actual directory of check families ๐Ÿ‘‘ โ€” every single one starts with a name prefix, and that prefix is literally how you'll filter them later. no cap, memorize these:

  • 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. (the "girl why would you write that" collection ๐Ÿ’€)
  • 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.
  • 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. ๐Ÿ™ˆ (yes that's the real description, iconic behavior)
  • 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.

๐ŸŒˆ clang diagnostics, the whole situation

Clang diagnostics are treated in a similar way as check diagnostics. Fr fr, same energy. Clang diagnostics are displayed by clang-tidy and can be filtered out using the -checks= option.

โš ๏ธ But here's the catch, bestie: the -checks= option does not affect compilation arguments, so it cannot turn on Clang warnings which are not already turned on in the build configuration. It can only vibe-check what's already there, iykyk.

The -warnings-as-errors= option upgrades any warnings emitted under the -checks= flag to errors (but it does not enable any checks itself). So it's a glow-up, not a summon. ๐Ÿ’…

Clang diagnostics have check names starting with clang-diagnostic-. Diagnostics which have a corresponding warning option, are named clang-diagnostic-<warning-option>, e.g. Clang warning controlled by -Wliteral-conversion will be reported with check name clang-diagnostic-literal-conversion. Slay, consistent naming.

Clang compiler errors (such as syntax errors, semantic errors, or other failures that prevent Clang from compiling the code) are reported with the check name clang-diagnostic-error. These represent fundamental compilation failures that must be fixed before clang-tidy can perform its analysis.

๐Ÿซถ Unlike other diagnostics, clang-diagnostic-error cannot be disabled, as clang-tidy requires valid code to function. Like yeah, you gotta have a whole file before anyone can read it. ๐Ÿ˜ญ

The -fix flag instructs clang-tidy to fix found errors if supported by corresponding checks. (only if the check is down to do the work, no pressure tho โœจ)


๐ŸŒธ an overview of all the command-line options

here she is, the full help dump, in all her glory:

$ 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:
  • --allow-no-checks โ€” lets you have zero enabled checks and not get yelled at ๐Ÿ’…. It suppresses that whole "no checks enabled" error you'd otherwise get when you disable every single check. Empty and unbothered, honestly iconic.
  • --checks=<string> โ€” a comma-separated list of globs, each one optionally prefixed with -. The globs get processed in the order they show up in the list. Globs without the - prefix add checks with matching names to the set; globs with the - prefix remove checks with matching names from the set of enabled checks. And bestie, this option's value is 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, like:
-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. Internally this option works exactly the same way as --config after reading the specified config file. Use either --config-file or --config, not both โ€” pick a lane, queen ๐Ÿ‘‘.
  • --dump-config โ€” dumps the configuration in YAML format to stdout. You can use this along with a file name (and '--' if the file is outside of a project with a configured compilation database). The configuration used for this file will be printed. Use it along with -checks=* to include the configuration of all the checks. Full lore drop ๐Ÿ“œ.
  • --enable-check-profile โ€” enables per-check timing profiles and prints a report to stderr. Time to find out which check is the drama queen taking up all the runtime ๐Ÿ˜ญ.
  • --enable-module-headers-parsing โ€” enables preprocessor-level module header parsing for C++20 and above, empowering specific checks to detect macro definitions within modules. โš ๏ธ Heads up though: this feature may cause performance and parsing issues and is therefore considered experimental. Proceed with caution, babes.
  • --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 the .clang-tidy file, if any. ๐Ÿšซ
  • --experimental-custom-checks โ€” enables experimental clang-query based custom checks. See https://clang.llvm.org/extra/clang-tidy/QueryBasedCustomChecks.html. Experimental era ๐Ÿ’€โœจ.
  • --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. Detective mode activated ๐Ÿ”.
  • --export-fixes=<filename> โ€” the YAML file to store suggested fixes in. The stored fixes can be applied to the input source code with clang-apply-replacements. Save those receipts ๐ŸŽ€.
  • --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. Front of the line, main character energy ๐Ÿ‘‘.
  • --fix โ€” apply suggested fixes. Without -fix-errors, clang-tidy will bail out if any compilation errors were found. No fixes for a broken build, sorry ๐Ÿซ .
  • --fix-errors โ€” apply suggested fixes even if compilation errors were found. If the compiler errors have attached fix-its, clang-tidy will apply them as well. Bold move, love that for you ๐Ÿ”ฅ.
  • --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. Sneaky little two-for-one ๐Ÿงš.
  • --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 the clang-format documentation for the up-to-date info about formatting styles and options. This option overrides the 'FormatStyle`` option in the .clang-tidy` file, if any. (Yes, that stray backtick is canon, we don't edit ๐Ÿ’…)

  • --header-filter=<string> โ€” regular expression matching the names of the headers to output diagnostics from. The default value is '.*', i.e. diagnostics from all non-system headers are displayed by default. 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 the .clang-tidy file, if any. ๐ŸŒˆ
  • --line-filter=<string> โ€” a list of files and line ranges to output diagnostics from. The range is inclusive on both ends. Can be used together with -header-filter. The format of the list is a JSON array of objects, for example:
[
  {"name":"file1.cpp","lines":[[1,3],[5,7]]},
  {"name":"file2.h"}
]

This will output diagnostics from 'file1.cpp' only for the line ranges [1,3] and [5,7], as well as all from the entire 'file2.h'. So specific, so precise ๐ŸŽฏ

  • --list-checks โ€” list all enabled checks and exit. Use it with -checks=* to list all available checks. The full menu ๐Ÿ“.
  • --load=<pluginfilename> โ€” load the specified plugin. Plug in, glow up ๐Ÿ”Œโœจ.
  • -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. Shhh, no spoilers ๐Ÿคซ.
  • --removed-arg=<string> โ€” list of arguments to remove from the command line sent to the compiler. โš ๏ธ Please note that removing arguments might change the semantics of the analyzed code, possibly leading to compiler errors, false positives or false negatives. This option is applied before --extra-arg and --extra-arg-before. Order matters, bestie, don't forget it ๐Ÿ˜ค.
  • --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. Data girlie behavior ๐Ÿ“Š๐Ÿ’œ.
  • --system-headers โ€” display the errors from system headers. This option overrides the 'SystemHeaders' option in the .clang-tidy file, if any. Nothing is safe from the diagnostics, not even the system ๐Ÿซฃ.
  • --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 the .clang-tidy file, if any. Aesthetic matters, obviously ๐ŸŽจ.
  • --verify-config โ€” check the config files to ensure each check and option is recognized, without running any checks. Validation era, no actual work required โœจ.
  • --vfsoverlay=<filename> โ€” overlay the virtual filesystem described by file over the real file system. Reality, but make it virtual ๐Ÿฆ‹.
  • --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 the .clang-tidy file, if any. Warnings graduating with honors ๐Ÿ‘‘๐Ÿ”ฅ.

-p <build-path> is used to read a compile command database. ๐Ÿซถ

For example, it can be a CMake build directory with a file named compile_commands.json in it (bestie, you get that output by passing the -DCMAKE_EXPORT_COMPILE_COMMANDS=ON CMake option โœจ). And if you don't specify a build path at all? It literally goes hunting for compile_commands.json through all the parent paths of the first input file. So it's giving "I'll find it myself, thanks." ๐Ÿ•ต๏ธโ€โ™€๏ธ

Need a worked example of setting up Clang Tooling on a source tree? They point you at: https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html โ€” go read it, it's iconic.

<source0> ... โ€” these specify the paths of your source files, and they get looked up in the compile command database. Rules, because of course there are rules:

  • If the path of a file is absolute, it needs to point into CMake's source tree.
  • If the path is relative, then your current working directory needs to be in the CMake source tree, and the file must live in a subdirectory of your current working directory. "./" prefixes in relative files get automatically removed (slay, one less thing), but the rest of a relative path must be a suffix of a path in the compile command database. No cap.

Parameters files: got an absurd number of options or source files? You can pass them as parameter files by using '@parameter-file' on the command line. Very demure, very mindful ๐Ÿ’…

Configuration files: clang-tidy tries to read configuration for each source file from a .clang-tidy file located in the closest parent directory of that source file ๐ŸŽ€. The .clang-tidy file is specified in YAML format. And โ€” important โ€” if any configuration option has a corresponding command-line option, the command-line option takes precedence. CLI wins, always, periodt.

The following configuration options may be used in a .clang-tidy file:

  • CheckOptions โ€” list of key-value pairs defining check-specific options. Example:
CheckOptions:
  some-check.SomeOption: 'some value'
  • Checks โ€” same as '--checks'. Bonus: the list of globs can be specified as a list instead of a string ๐Ÿ“.
  • CustomChecks โ€” array of user defined checks based on Clang-Query syntax.
  • ExcludeHeaderFilterRegex โ€” same as '--exclude-header-filter'.
  • ExtraArgs โ€” same as '--extra-arg'.
  • ExtraArgsBefore โ€” same as '--extra-arg-before'.
  • FormatStyle โ€” same as '--format-style'.
  • HeaderFileExtensions โ€” file extensions to consider to determine if a given diagnostic is located in a header file.
  • HeaderFilterRegex โ€” same as '--header-filter'.
  • ImplementationFileExtensions โ€” file extensions to consider to determine if a given diagnostic is located in an implementation file.
  • InheritParentConfig โ€” if this option is true in a config file, the configuration file in the parent directory (if any exists) will be taken and the current config file will be applied on top of the parent one. It's giving generational inheritance, honestly ๐Ÿงฌ.
  • RemovedArgs โ€” same as '--removed-arg'.
  • 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. Yes, it will @ you in the code ๐Ÿ˜ญ.
  • WarningsAsErrors โ€” same as '--warnings-as-errors'.

Wanna see the effective configuration with your own eyes? Peep it with --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'
...

๐Ÿฆ‹ Running Clang-Tidy on CUDA Files

clang-tidy supports analyzing CUDA source files โ€” GPU girlies rise up ๐Ÿ’œ. To ensure correct header resolution, it is important to specify the CUDA toolkit path using --cuda-path. For more details on how Clang handles CUDA, see Compiling CUDA with Clang.

If you're using a GCC + NVCC build setup, the compiler command database will contain NVCC-specific flags that clang-tidy does not understand. Awkward. ๐Ÿ™ˆ

In this case, you should use the RemovedArgs configuration option (or --removed-arg command-line option) to remove these flags, and ExtraArgs (or --extra-arg) to provide the --cuda-path.

For example, to remove the NVCC-specific -gencode flag and provide the CUDA path:

$ clang-tidy source.cu --removed-arg="-gencode" --removed-arg="arch=.." --extra-arg="--cuda-path=/path/to/cuda"

By default, clang-tidy will analyze both host and device code. To restrict the analysis to a specific side and specifically choose device compilation flags, use the --extra-arg flag to pass the arguments.

For example, to perform device analysis only, use the --cuda-device-only flag:

$ clang-tidy source.cu --extra-arg="--cuda-device-only" --extra-arg="--cuda-path=/path/to/cuda"

๐ŸŽ€ Clang-Tidy Automation

clang-tidy can analyze multiple source files by specifying them on the command line. But for larger projects, automation scripts provide additional functionality like parallel execution and integration with version control systems. Because nobody's out here running one file at a time, be so fr.

โšก Running Clang-Tidy in Parallel

clang-tidy can process multiple files sequentially, but for projects with many source files, the run-clang-tidy.py script provides parallel execution to significantly reduce analysis time. This script is included with clang-tidy and runs clang-tidy over all files in a compilation database or a specified path concurrently. Speed, slay, etc. ๐Ÿ”ฅ

The script requires a compilation database (compile_commands.json) which can be generated by build systems like CMake (using -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) or by tools like Bear ๐Ÿป.

The script supports most of the same options as clang-tidy itself, including -checks=, -fix, -header-filter=, and configuration options. Run run-clang-tidy.py --help for a complete list of available options.

Example invocations:

# Run clang-tidy on all files in the compilation database in parallel
$ run-clang-tidy.py -p=build/

# Run with specific checks and apply fixes
$ run-clang-tidy.py -p=build/ -fix -checks=-*,readability-*

# Run on specific files/directories with header filtering
$ run-clang-tidy.py -p=build/ -header-filter=src/ src/

# Run with parallel execution (uses all CPU cores by default)
$ run-clang-tidy.py -p=build/ -j 4

๐ŸŒˆ Running Clang-Tidy on Diff

The clang-tidy-diff.py script allows you to run clang-tidy on the lines that have been modified in your working directory or in a specific diff. And listen โ€” importantly, clang-tidy-diff.py only reports diagnostics for changed lines; clang-tidy still analyzes the entire file and filters out unchanged lines after analysis, so this does not improve performance. Read that again, bestie, it's not a speed hack ๐Ÿ˜ญ. It's particularly useful for code reviews and continuous integration, as it focuses analysis on the changed code rather than the entire codebase. Main character energy for your PR.

The script can work with various diff sources:

  • Git working directory changes
  • Output from git diff
  • Output from svn diff
  • Patch files

Example invocations:

# Run clang-tidy on all changes in the working directory
$ git diff -U0 --no-color HEAD^ | clang-tidy-diff.py -p1

# Run with specific checks and apply fixes
$ git diff -U0 --no-color HEAD^ | clang-tidy-diff.py -p1 -fix \
  -checks=-*,readability-*

# Run on staged changes
$ git diff -U0 --no-color --cached | clang-tidy-diff.py -p1

# Run on changes between two commits
$ git diff -U0 --no-color HEAD~2 HEAD | clang-tidy-diff.py -p1

# Run on a patch file
$ clang-tidy-diff.py -p1 < changes.patch

The -p1 option tells the script to strip one level of path prefix from the diff, which is typically needed for Git diffs. The script supports most of the same options as clang-tidy itself, including -checks=, -fix, -header-filter=, and configuration options.

โš ๏ธ Okay so this is the part where you need to listen: while clang-tidy-diff.py is useful for focusing on recent changes, relying solely on it may lead to incomplete analysis. Since the script only reports warnings from the modified lines, it may miss issues that are caused by the changes but manifest elsewhere in the code. For example, changes that only add lines to a function may cause it to violate size limits (e.g., readability-function-size), but the diagnostic will be reported at the function declaration, which may not be in the diff and thus filtered out. Modifications to header files may also affect many implementation files, but only warnings in the modified header lines will be reported. ๐Ÿ’€

For comprehensive analysis, especially before merging significant changes, consider running clang-tidy on the entire affected files or the whole project using run-clang-tidy.py. Do the full sweep before you ship, queen ๐Ÿ‘‘.

๐ŸŒธ 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 if the code is known to be correct, it may be useful to silence the warning. Some clang-tidy checks provide a check-specific way to silence the diagnostics, e.g. bugprone-use-after-move 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. Very "I fixed it at the source" of them โœจ.

If a specific suppression mechanism is not available for a certain warning, or its use is not desired for some reason, clang-tidy has a generic mechanism to suppress diagnostics using NOLINT, NOLINTNEXTLINE, and NOLINTBEGIN ... NOLINTEND comments. The holy trinity of "not today" ๐Ÿ™…โ€โ™€๏ธ.

okay so the NOLINT comment is literally telling clang-tidy "bestie, not this line" ๐Ÿ˜Œ and it's line-scoped only โ€” it does not apply to a function, a block of code or any other language construct; it applies to the line of code it is on. And if dropping the comment on that same line would mess up your formatting in a way you hate, the NOLINTNEXTLINE comment lets you suppress clang-tidy warnings on the next line. Then NOLINTBEGIN and NOLINTEND are the duo that lets you suppress clang-tidy warnings on multiple lines (affecting all lines between the two comments). multi-line slay ๐Ÿ’…

All comments can be followed by an optional list of check names in parentheses (see below for the formal syntax, it's down there ๐Ÿ‘‡). That check name list supports globbing, with the same format and semantics as for enabling checks. Heads up though: negative globs are ignored here, because they would effectively re-activate the warning. so like, that's the whole opposite of what you want ๐Ÿ™ˆ

For example:

class Foo {
  // Suppress all the diagnostics for the line
  Foo(int param); // NOLINT

  // Consider explaining the motivation to suppress the warning
  Foo(char param); // NOLINT: Allow implicit conversion from `char`, because <some valid reason>

  // Silence only the specified checks for the line
  Foo(double param); // NOLINT(google-explicit-constructor, google-runtime-int)

  // Silence all checks from the `google` module
  Foo(bool param); // NOLINT(google*)

  // Silence all checks ending with `-avoid-c-arrays`
  int array[10]; // NOLINT(*-avoid-c-arrays)

  // Silence only the specified diagnostics for the next line
  // NOLINTNEXTLINE(google-explicit-constructor, google-runtime-int)
  Foo(bool param);

  // Silence all checks from the `google` module for the next line
  // NOLINTNEXTLINE(google*)
  Foo(bool param);

  // Silence all checks ending with `-avoid-c-arrays` for the next line
  // NOLINTNEXTLINE(*-avoid-c-arrays)
  int array[10];

  // Silence only the specified checks for all lines between the BEGIN and END
  // NOLINTBEGIN(google-explicit-constructor, google-runtime-int)
  Foo(short param);
  Foo(long param);
  // NOLINTEND(google-explicit-constructor, google-runtime-int)

  // Silence all checks from the `google` module for all lines between the BEGIN and END
  // NOLINTBEGIN(google*)
  Foo(bool param);
  // NOLINTEND(google*)

  // Silence all checks ending with `-avoid-c-arrays` for all lines between the BEGIN and END
  // NOLINTBEGIN(*-avoid-c-arrays)
  int array[10];
  // NOLINTEND(*-avoid-c-arrays)
};

The formal syntax of NOLINT, NOLINTNEXTLINE, and NOLINTBEGIN ... NOLINTEND is the following:

lint-comment:
  lint-command
  lint-command lint-args

lint-args:
  ( check-name-list )

check-name-list:
  check-name
  check-name-list , check-name

lint-command:
  NOLINT
  NOLINTNEXTLINE
  NOLINTBEGIN
  NOLINTEND

Note that whitespaces between NOLINT/NOLINTNEXTLINE/NOLINTBEGIN/NOLINTEND and the opening parenthesis are not allowed (in this case the comment will be treated just as NOLINT/NOLINTNEXTLINE/NOLINTBEGIN/NOLINTEND), whereas in the check names list (inside the parentheses), whitespaces can be used and will be ignored. so it's giving "space where it counts, no space where it doesn't" โœจ

All NOLINTBEGIN comments must be paired by an equal number of NOLINTEND comments. Moreover, a pair of comments must have matching arguments โ€” for example, NOLINTBEGIN(check-name) can be paired with NOLINTEND(check-name) but not with NOLINTEND (zero arguments). clang-tidy will generate a clang-tidy-nolint error diagnostic if any NOLINTBEGIN/NOLINTEND comment violates these requirements. so like, keep your pairs matching or it will absolutely call you out ๐Ÿ˜ญ


๐Ÿฆ‹ CLANG-INCLUDE-FIXER ๐Ÿฆ‹

๐ŸŒธ Contents ๐ŸŒธ

  • Clang-Include-Fixer
    • Setup
      • Creating a Symbol Index From a Compilation Database
      • Integrate with Vim
      • Integrate with Emacs
    • How it Works

One of the major nuisances of C++ compared to other languages is the manual management of #include directives in any file. 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. honestly iconic behaviour, we love a tool that handles the boring part ๐Ÿซถ

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.

  • 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. 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.

$ 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 #includes to show. Default is 3.

  • let g:clang_include_fixer_increment_num = 5

Set the increment number of #includes 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. fr fr that's the whole trick โ€” catch it early while the info is still there ๐Ÿ‘‘

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. one at a time, bestie, we're being careful out here ๐Ÿ’…

Contents

  • Clang-Change-Namespace
  • Example usage
  • Another example
    • Caveats
  • Content already exists in new namespace
  • Inline namespace doesn't work
  • Symbol references not updated
    • clang-change-namespace Command Line Options

clang-change-namespace is the tool that changes the surrounding namespaces of class/function definitions. Bestie, it literally picks your classes up and moves them to a new namespace home ๐Ÿ โœจ

Classes/functions in the moved namespace will have new namespaces, while references to symbols (e.g. types, functions) that are not defined in the changed namespace get correctly qualified by prepending namespace specifiers before them. And it tries to add the shortest namespace specifiers possible โ€” efficiency icon behavior ๐Ÿ’…

When a symbol reference needs to be fully-qualified, this adds a :: prefix to the namespace specifiers unless the new namespace is the global namespace. For classes, only classes that are declared/defined in the given namespace in specified files will be moved: forward declarations will remain in the old namespace. This will be demonstrated in the next example ๐Ÿ‘€

Example usage

For example, consider this test.cc example here with the forward declared class FWD and the defined class A, both in the namespace a:

namespace a {
class FWD;
class A {
  FWD *fwd;
};
} // namespace a

And now let's change the namespace a to x ๐Ÿฆ‹

clang-change-namespace \
  --old_namespace "a" \
  --new_namespace "x" \
  --file_pattern "test.cc" \
  --i \
  test.cc

Note that in the code below there's still the forward declared class FWD that stayed in the namespace a. It wasn't moved to the new namespace because it wasn't defined/declared here in a but only forward declared ๐Ÿ˜Œ

namespace a {
class FWD;
} // namespace a
namespace x {

class A {
  a::FWD *fwd;
};
} // namespace x

Another example

Consider this test.cc file:

namespace na {
class X {};
namespace nb {
class Y {
  X x;
};
} // namespace nb
} // namespace na

To move the definition of class Y from namespace na::nb to x::y, run:

clang-change-namespace \
  --old_namespace "na::nb" \
  --new_namespace "x::y" \
  --file_pattern "test.cc" \
  --i \
  test.cc

This will overwrite test.cc to look like this:

namespace na {
class X {};

} // namespace na
namespace x {
namespace y {
class Y {
  na::X x;
};
} // namespace y
} // namespace x

Note that we've successfully moved the class Y from namespace na::nb to namespace x::y โ€” slay, it ate and left no crumbs ๐ŸŽ€

Caveats

Content already exists in new namespace

Consider this test.cc example that defines two class A, one inside the namespace a and one in namespace b:

namespace a {
class A {
    int classAFromWithinNamespace_a;
};
} // namespace a

namespace b {
class A {
    int classAFromWithinNamespace_b;
};
} //namespace b

Let's move everything from namespace a to namespace b:

clang-change-namespace \
  --old_namespace "a" \
  --new_namespace "b" \
  --file_pattern test.cc \
  test.cc

As expected we now have to definitions of class A inside the namespace b:

namespace b {
class A {
  int classAFromWithinNamespace_a;
};
} // namespace b

namespace b {
class A {
    int classAFromWithinNamespace_b;
};
} //namespace b

The re-factoring looks correct but the code will not compile due to the name duplication ๐Ÿ˜ญ It is not up to the tool to ensure compilability in that sense. But one has to be aware of that. Periodt.

Inline namespace doesn't work

Consider this usage of two versions of implementations for a greet function:

#include <cstdio>

namespace Greeter {
inline namespace Version1 {
  const char* greet() { return "Hello from version 1!"; }
} // namespace Version1
namespace Version2 {
  const char* greet() { return "Hello from version 2!"; }
} // namespace Version2
} // namespace Greeter

int main(int argc, char* argv[]) {
  printf("%s\n", Greeter::greet());
  return 0;
}

Note that currently Greeter::greet() will result in a call to Greeter::Version1::greet() because that's the inlined namespace ๐Ÿงš

Let's say you want to move one and make Version2 the default now and remove the inline from the Version1. First let's try to turn namespace Version2 into inline namespace Version2:

clang-change-namespace \
  --old_namespace "Greeter::Version2" \
  --new_namespace "inline Version2" \
  --file_pattern main.cc main.cc

But this will put the inline keyword in the wrong place resulting in:

#include <cstdio>

namespace Greeter {
inline namespace Version1 {
        const char* greet() { return "Hello from version 1!"; }
} // namespace Version1

} // namespace Greeter
namespace inline Greeter {
namespace Version2 {
const char *greet() { return "Hello from version 2!"; }
} // namespace Version2
} // namespace inline Greeter

int main(int argc, char* argv[]) {
        printf("%s\n", Greeter::greet());
        return 0;
}

You cannot use clang-change-namespace to inline a namespace ๐Ÿ’€ yeah, that's a no from her.

Symbol references not updated

Consider this test.cc file:

namespace old {
struct foo {};
}  // namespace old

namespace b {
old::foo g_foo;
}  // namespace b

Notice that namespace b defines a global variable of type old::foo. If we now change the name of the old namespace to modern, the reference will not be updated ๐Ÿ™ˆ

clang-change-namespace \
  --old_namespace "old" \
  --new_namespace "modern" \
  --file_pattern test.cc \
  test.cc
namespace modern {
struct foo {};
} // namespace modern

namespace b {
old::foo g_foo;
}  // namespace b

g_foo is still of the no longer existing type old::foo, while instead it should use modern::foo ๐Ÿ˜ญ

Only symbol references in the moved namespace are updated, not outside of it. Lowkey the most important sentence here, bestie โ€” don't forget it.

clang-change-namespace Command Line Options

  • --allowed_file=<string> โ€” a file containing regexes of symbol names that are not expected to be updated when changing namespaces around them ๐Ÿ“
  • --dump_result โ€” dump new file contents in YAML, if specified.
  • --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.
  • --file_pattern=<string> โ€” only rename namespaces in files that match the given regular expression pattern.
  • -i โ€” inplace edit <file>s, if specified.
  • --new_namespace=<string> โ€” new namespace. Use "" when you target the global namespace ๐ŸŒˆ
  • --old_namespace=<string> โ€” old namespace.
  • -p <string> โ€” build path.
  • --style=<string> โ€” the style name used for reformatting โœจ

๐Ÿ’… CLANG-REORDER-FIELDS ๐Ÿ’…

๐Ÿ“‹ Contents

  • Clang-Reorder-Fields
    • Example usage
      • Basic struct reordering
      • Namespaced structs
      • C++ constructor initializer lists
      • Designated initializers
      • In-place editing
    • Limitations and Caveats
      • Different access specifiers
      • Multiple field declarations
      • Macro-expanded fields
      • Preprocessor directives
      • Flexible array members
      • Field dependencies in initializers
    • clang-reorder-fields Command Line Options
    • Use Cases
      • Memory layout optimization
      • Coding standard compliance
      • Field grouping

okay bestie, buckle up โ€” clang-reorder-fields is a refactoring tool that reorders fields in C/C++ structs and classes. And the way it just handles everything for you? Obsessed. This tool automatically updates:

  • Field declarations in the record definition
  • Constructor initializer lists in C++ classes
  • Aggregate initialization expressions (both C and C++)
  • Designated initializer lists (C++20)

This can be useful for optimizing memory layout, improving cache performance, or conforming to coding standards that require specific field orderings. lowkey it's giving "I fixed your padding while you weren't looking" energy. โœจ


๐ŸŒธ Example usage ๐ŸŒธ

๐ŸŽ€ Basic struct reordering

Consider this simple struct in example.c:

struct Foo {
  const int *x;
  int y;
  double z;
  int w;
};

int main() {
  const int val = 42;
  struct Foo foo = { &val, 0, 1.5, 17 };
  return 0;
}

To reorder the fields to z, w, y, x, run:

clang-reorder-fields -record-name Foo -fields-order z,w,y,x example.c --

This will reorder both the struct definition and the initialization:

struct Foo {
  double z;
  int w;
  int y;
  const int *x;
};

int main() {
  const int val = 42;
  struct Foo foo = { 1.5, 17, 0, &val };
  return 0;
}

like?? it moved the 1.5 and the &val too, without you touching the initializer at all. slay. ๐Ÿ’…

๐Ÿฆ‹ Namespaced structs

For C++ code with namespaces, use the fully-qualified name:

namespace bar {
struct Foo {
  const int *x;
  int y;
  double z;
  int w;
};
}

clang-reorder-fields -record-name ::bar::Foo -fields-order z,w,y,x example.cpp --

For classes defined in the global namespace (without any namespace), you can use either the simple class name or prefix it with :::

clang-reorder-fields -record-name Foo -fields-order z,w,y,x example.cpp --
# or
clang-reorder-fields -record-name ::Foo -fields-order z,w,y,x example.cpp --

๐Ÿงš C++ constructor initializer lists

The tool also reorders constructor initializer lists. Given:

class Foo {
public:
  Foo();

private:
  int x;
  const char *s1;
  const char *s2;
  double z;
};

Foo::Foo():
  x(12),
  s1("abc"),
  s2("def"),
  z(3.14)
{}

Running:

clang-reorder-fields -record-name Foo -fields-order s1,x,z,s2 example.cpp --

Will reorder both the field declarations and the constructor initializers:

class Foo {
public:
  Foo();

private:
  const char *s1;
  int x;
  double z;
  const char *s2;
};

Foo::Foo():
  s1("abc"),
  x(12),
  z(3.14),
  s2("def")
{}

notice how the initializer order followed the fields, not the other way around? that's the whole vibe. the tool respects the language, not your chaos. ๐Ÿซถ

๐Ÿ“ Designated initializers

For C++20 code using designated initializers:

struct Bar {
  char a;
  int b;
  int c;
};

int main() {
  Bar bar1 = { 'a', 0, 123 };
  Bar bar2 = { .a = 'a', .b = 0, .c = 123 };
  return 0;
}

clang-reorder-fields --extra-arg="-std=c++20" -record-name Bar \
  -fields-order c,a,b example.cpp --

Will produce:

struct Bar {
  int c;
  char a;
  int b;
};

int main() {
  Bar bar1 = { 123, 'a', 0 };
  Bar bar2 = { .c = 123, .a = 'a', .b = 0 };
  return 0;
}

both the positional AND the designated initializers get remixed. the designators literally renamed themselves. iconic. โญ

๐ŸŒˆ In-place editing

Use the -i flag to modify files in-place:

clang-reorder-fields -record-name Foo -fields-order z,w,y,x -i example.c --

no more piping to a temp file like a Victorian orphan. we love convenience. ๐Ÿ’–


๐Ÿ’€ Limitations and Caveats ๐Ÿ’€

okay now the part where I hold your hand, because these are real and they will bite you. ๐Ÿ™ˆ

๐ŸŽ€ Different access specifiers

The tool cannot reorder fields with different access specifiers (public/private/protected). All fields being reordered must have the same access level.

class Example {
private:
  int x;
public:
  int y;  // Cannot reorder x and y - different access levels
};

๐Ÿ’… Multiple field declarations

Declarations with multiple fields in one statement are not supported:

struct Example {
  int a, b;  // Not supported - multiple fields in one declaration
};

๐Ÿงš Macro-expanded fields

Macros that expand to multiple field declarations are not supported. However, macros that expand to a single field declaration work correctly:

#define INT_FIELD(NAME) int NAME     // Supported - expands to one field
#define TWO_FIELDS int a; int b;     // Not supported - expands to two fields

struct Supported {
  INT_FIELD(x);  // OK - this is a single field
  int y;
  INT_FIELD(z);  // OK - this is a single field
};

struct NotSupported {
  TWO_FIELDS     // Not OK - expands to multiple fields
  int c;
};

The tool can reorder fields declared via macros as long as each macro invocation expands to exactly one field declaration. one macro = one field. that's the rule, bestie.

๐Ÿฆ‹ Preprocessor directives

Structs with preprocessor directives between fields cannot be reordered:

struct Example {
  int a;
#ifdef FEATURE
  int b;
#endif
  int c;  // Not supported - preprocessor directives present
};

๐Ÿ“ Flexible array members

In C, a flexible array member is an incomplete array type that must be the last member of a struct (as specified by C99 and later standards). This allows the struct to have a variable-length array at the end. Since this is a language requirement, the tool enforces that flexible array members remain in the last position:

struct Example {
  int count;
  int data[];  // Flexible array member - must remain last
};

Attempting to reorder fields such that the flexible array member is no longer last will result in an error:

clang-reorder-fields -record-name Example -fields-order data,count example.c --

Will produce:

Flexible array member must remain the last field in the struct

This ensures the generated code remains valid C. no cap, it's just protecting you from yourself. ๐Ÿซถ

๐Ÿ”ฅ Field dependencies in initializers

The tool will issue a warning if reordering causes a field to be used in an initializer before it's initialized. Consider this example:

class Foo {
public:
  Foo(int x, char c);
  int x;
  char c;
  Dummy z;
};

Foo::Foo(int x, char c) :
  x(x),
  c(c),
  z(this->x, c)  // z's initializer uses x and c
{}

If you reorder the fields to z, c, x:

clang-reorder-fields -record-name Foo -fields-order z,c,x example.cpp --

The tool will produce warnings:

example.cpp:10:3: warning: reordering field x after z makes x uninitialized when used in init expression
example.cpp:10:3: warning: reordering field c after z makes c uninitialized when used in init expression

This warns you that in C++, member initializers are executed in the order that fields are declared in the class, not the order they appear in the initializer list. After reordering, z would be initialized first, but its initializer tries to use x and c which haven't been initialized yet. iykyk ๐Ÿ˜ญ

The tool will still perform the reordering but warns about the potential issue. You should review these warnings and adjust your code accordingly.

like, it warns you and then does it anyway. so READ the warnings. please. for me. ๐Ÿ’€


โœจ clang-reorder-fields Command Line Options โœจ

  • --record-name=<string> โ€” The fully-qualified name of the struct or class to reorder. Required.
    • For C structs, use the struct name directly (e.g., Foo).
    • For C++ classes/structs in namespaces, use the fully-qualified name including namespaces (e.g., ::namespace::ClassName).
    • For C++ classes/structs in the global namespace, you can use either the simple name (e.g., Foo) or prefix with :: (e.g., ::Foo).
  • --fields-order=<string> โ€” Comma-separated list of field names in the desired order. Required.
    • All field names must exactly match the fields in the struct/class definition. The number of fields must match the number in the definition.
  • -i โ€” Overwrite edited files in-place. If not specified, the rewritten code is printed to stdout.
  • --extra-arg=<string> โ€” Additional argument to append to the compiler command line.
    • Useful for specifying language standards (e.g., --extra-arg="-std=c++20").
  • --extra-arg-before=<string> โ€” Additional argument to prepend to the compiler command line.
  • -p <string> โ€” Build path. Specifies the directory containing compile_commands.json for compilation database support.

that's the whole menu, bestie. six lil options and they carry the entire operation. ๐Ÿ‘‘


๐ŸŒˆ Use Cases ๐ŸŒˆ

๐Ÿ’œ Memory layout optimization

Reorder fields to minimize padding and improve cache locality:

// Before: 24 bytes (with padding)
struct Data {
  char a;     // 1 byte + 7 padding
  double b;   // 8 bytes
  char c;     // 1 byte + 7 padding
};

clang-reorder-fields -record-name Data -fields-order b,a,c data.c --

// After: 16 bytes (less padding)
struct Data {
  double b;   // 8 bytes
  char a;     // 1 byte
  char c;     // 1 byte + 6 padding
};

24 bytes โ†’ 16 bytes. that's 8 whole bytes back. FREE. REAL ESTATE. ๐Ÿ”ฅ

๐Ÿงš Coding standard compliance

Ensure fields are ordered according to project conventions (e.g., alphabetically, by type, or by access pattern). your linter will finally stop yelling at you.

๐ŸŽ€ Field grouping

Group related fields together for better code organization and readability. aesthetic AND functional. we stay winning. ๐Ÿ’…


๐Ÿฆ‹ MODULARIZE USER'S MANUAL ๐Ÿฆ‹

๐ŸŒธ Modularize Usage ๐ŸŒธ

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.

so like โ€” it peeks inside your module map, pulls out the header names, and checks the normal ones. the ones wearing the "private"/"textual"/"exclude" name tags get left alone, because there's a reason they're dressed like that. iykyk. ๐Ÿซถ

<include-files-list> is literally just the path to a file that has a newline-separated list of headers you want checked against each other ๐Ÿซถ. Lines starting with '#' and empty lines get ignored (they're just vibes, not data). If a header file name is followed by a colon and then some space-separated file names, those extra files get pulled in as dependencies ๐Ÿ’…. The file names can be relative or full paths, but the way that they must be on the same line โ€” non-negotiable. For example:

header1.h
header2.h
header3.h: header1.h header2.h
Heads up bestie ๐ŸŽ€ โ€” unless you pass a -prefix (header path) option, any non-absolute file paths in the header list file will be treated as relative to the header list file's directory. Use -prefix if you want a different directory.

<front-end-options> is a little placeholder spot for regular Clang front-end arguments, and the way that they have to come after the <include-files-list>? Iconic, but also important. Ngl, by default modularize assumes .h files contain C++ source, so if you're working in a different language you might need a -x option to tell Clang the header has another language in it, like: -x c

Also โ€” and this is the part people forget ๐Ÿ˜ญ โ€” because modularize does not use the clang driver, you'll probably need to pass in extra compiler front-end arguments yourself to match whatever the driver would've passed in by default. Fr fr, don't skip this.

๐Ÿ’– Modularize Command Line Options ๐Ÿ’–

  • -prefix=<header-path> โ€” Prepends 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, so use -prefix when you want to point at a different directory.
  • -module-map-path=<module-map-path> โ€” Generates a module map and outputs it to the given file ๐Ÿ’…. See the description over in Module Map Generation.
  • -problem-files-list=<problem-files-list-file-name> โ€” Strictly for use with the module map assistant ๐Ÿซถ. You feed it a list of files that have problems with respect to modules. Those files still end up in the generated module map, but they get marked as "excluded" headers.
  • -root-module=<root-name> โ€” Puts the modules generated by the -module-map-path option inside one enclosing module with the given name ๐Ÿ‘‘. See the description in Module Map Generation.
  • -block-check-header-list-only โ€” Limits the #include-inside-extern-or-namespace-block check to only those headers explicitly listed in the header list ๐ŸŒธ. It's a work-around so you don't get error messages for private includes that purposefully get included inside blocks (iykyk).
  • -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 โ€” Displays lists of good files (no compile errors), problem files, and a combined list where problem files are preceded by a '#' ๐Ÿ’€. This one's a cheat code for quickly figuring out which files have problems. That combined list is lowkey great for starting to modularize a set of headers: start with a full list of headers, run the -display-file-lists option, then use the combined list as your intermediate list, uncommenting-out headers as you fix them. Periodt.

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 make modules built from the headers behave poorly, and they should be fixed before you introduce a module map. No cap.

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 you can absolutely use as a starting point for a module.modulemap file.

๐ŸŒˆ Getting Started ๐ŸŒˆ

To build from source:

  1. Read Getting Started with the LLVM System and Clang Tools Documentation for the info on getting sources for LLVM, Clang, and Clang Extra Tools ๐Ÿ’–.
  2. Getting Started with the LLVM System and Building LLVM with CMake give you the directions for how to build ๐Ÿ› ๏ธ. Once sources are all checked out into the right place, the LLVM build will build Clang Extra Tools and their dependencies automatically.
    • If using CMake, you can also use the modularize target to build just the modularize tool and its dependencies ๐Ÿ‘‘

Before you keep going, take a look at Modularize Usage to see how to invoke modularize ๐Ÿซถ.

๐Ÿฆ‹ What Modularize Checks ๐Ÿฆ‹

modularize will check for the following:

  • Duplicate global type and variable definitions ๐Ÿ’…
  • Duplicate macro definitions ๐Ÿ’…
  • Macro instances, 'defined(macro)', or #if, #elif, #ifdef, #ifndef conditions that evaluate differently in a header ๐Ÿ˜ญ
  • #include directives inside 'extern "C/C++" {}' or 'namespace (name) {}' blocks ๐Ÿ’€
  • Module map header coverage completeness (in the case of a module map input only) โœจ

modularize will do normal C/C++ parsing, reporting normal errors and warnings, but it will also report special error messages like the following:

error: '(symbol)' defined at multiple locations:
   (file):(row):(column)
   (file):(row):(column)

error: header '(file)' has different contents depending on how it was included

The latter might be followed by messages like the following:

note: '(symbol)' in (file) at (row):(column) not always provided

Checks will also be performed for macro expansions, defined(macro) expressions, and preprocessor conditional directives that evaluate inconsistently ๐Ÿ˜ค, and can produce error messages like the following:

   (...)/SubHeader.h:11:5:
#if SYMBOL == 1
    ^
error: Macro instance 'SYMBOL' has different values in this header,
       depending on how it was included.
  'SYMBOL' expanded to: '1' with respect to these inclusion paths:
    (...)/Header1.h
      (...)/SubHeader.h
(...)/SubHeader.h:3:9:
#define SYMBOL 1
        ^
Macro defined here.
  'SYMBOL' expanded to: '2' with respect to these inclusion paths:
    (...)/Header2.h
        (...)/SubHeader.h
(...)/SubHeader.h:7:9:
#define SYMBOL 2
        ^
Macro defined here.

Checks will also be performed for '#include' directives that are nested inside 'extern "C/C++" {}' or 'namespace (name) {}' blocks ๐ŸŒธ, and can produce error messages like the following:

IncludeInExtern.h:2:3:
#include "Empty.h"
^
error: Include directive within extern "C" {}.
IncludeInExtern.h:1:1:
extern "C" {
^
The "extern "C" {}" block is here.

๐ŸŽ€ Module Map Coverage Check ๐ŸŽ€

The coverage check uses the Clang library to read and parse the module map file ๐Ÿซถ. Starting at the module map file directory, or just the include paths (if specified), it collects the names of all the files it considers headers (no extension, .h, or .inc โ€” if you need more, modify the isHeader function ๐Ÿ‘€). Then it compares the headers against those referenced in the module map, either explicitly named, or implicitly named via an umbrella directory or umbrella file, as parsed by the ModuleMap object.

If headers are found which are not referenced or covered by an umbrella directory or file, warning messages get produced and this program returns an error code of 1 ๐Ÿ˜ญ. If no problems are found, an error code of 0 is returned. Slay.

Note that in the case of umbrella headers, this tool invokes the compiler to preprocess the file, and uses a callback to collect the header files included by the umbrella header or any of its nested includes ๐Ÿ’…. If any front end options are needed for these compiler invocations, they can be included on the command line after the module map file argument.

Warning messages have the form:

warning: module.modulemap does not account for file: Level3A.h
Heads up: for the case of the module map referencing a file that does not exist, the module map parser in Clang will (at the time of this writing) display an error message ๐Ÿ’€.

To limit the checks modularize does to just the module map coverage check, use the -coverage-check-only option โœจ.

For example:

modularize -coverage-check-only module.modulemap

๐Ÿ‘‘ Module Map Generation ๐Ÿ‘‘

If you specify the -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. Also, if the header in the header list is a partial path, a nested module hierarchy gets created in which a module is created for each subdirectory component in the header path, with the header itself represented by the innermost module ๐ŸŽ€. If other headers use the same subdirectories, they get enclosed in these same modules too.

For example, for the header list:

SomeTypes.h
SomeDecls.h
SubModule1/Header1.h
SubModule1/Header2.h
SubModule2/Header3.h
SubModule2/Header4.h
SubModule2.h

The following module map will be generated:

// Output/NoProblemsAssistant.txt
// Generated by: modularize -module-map-path=Output/NoProblemsAssistant.txt \
     -root-module=Root NoProblemsAssistant.modularize

module SomeTypes {
  header "SomeTypes.h"
  export *
}
module SomeDecls {
  header "SomeDecls.h"
  export *
}
module SubModule1 {
  module Header1 {
    header "SubModule1/Header1.h"
    export *
  }
  module Header2 {
    header "SubModule1/Header2.h"
    export *
  }
}
module SubModule2 {
  module Header3 {
    header "SubModule2/Header3.h"
    export *
  }
  module Header4 {
    header "SubModule2/Header4.h"
    export *
  }
  header "SubModule2.h"
  export *
}

An optional -root-module=<root-name> option can be used to cause a root module to be created which encloses all the modules ๐Ÿ‘‘.

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 the problem headers can be 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 *
            }
          }

Heads up bestie ๐Ÿ’… โ€” headers that have dependents get ignored with a warning, because the Clang module mechanism genuinely does not support headers that rely on other headers being included first. Like, it's not being rude, it just can't. So no cap, if you see that warning, that's why.

Also the module map format has some keywords that are โœจliterally forbiddenโœจ as module names. If a header has one of those names, an underscore ('_') gets prepended to the name. Example: if the header name is header.h, then because header is a keyword, the module name will be _header. For a full list of the module map keywords, please see: Lexical structure.


๐ŸŒˆ PP-TRACE USER'S MANUAL ๐ŸŒˆ

pp-trace is a standalone tool that traces preprocessor activity. It also doubles as a test of Clang's PPCallbacks interface. It runs a given source file through the Clang preprocessor, displaying selected information from callback functions overridden in a PPCallbacks derivation. The output is in a high-level YAML format, described 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>. (order matters, bestie, don't fight it ๐Ÿ˜ญ)

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 callbacks from the set. ๐Ÿ’–

The callbacks, in all their glory:

  • 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 https://yaml.org/ 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. iykyk โœจ

๐Ÿ’œ 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. ๐Ÿ™ˆ (we're not doing the whole struct, bestie, we'd be here all day)

๐ŸŒธ 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 โ€” The location of the directive.
  • Reason โ€” Argument Value Syntax: (EnterFile|ExitFile|SystemHeaderPragma|RenameFile) โ€” Clang C++ Type: PPCallbacks::FileChangeReason โ€” Reason for change.
  • FileType โ€” Argument Value Syntax: (C_User|C_System|C_ExternCSystem) โ€” Clang C++ Type: SrcMgr::CharacteristicKind โ€” Include type.
  • PrevFID โ€” Argument Value Syntax: ((file)|(invalid)) โ€” Clang C++ Type: FileID โ€” 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. (the classic "already been there, done that" move ๐Ÿ‘‘)

Argument descriptions:

  • ParentFile โ€” Argument Value Syntax: ("(file)" or (null)) โ€” Clang C++ Type: const FileEntry โ€” The file that #included the skipped file.
  • FilenameTok โ€” type is (token), stored as const Token. It's the token in ParentFile that points at the file that got skipped. That's the whole vibe ๐Ÿ’…
  • FileType โ€” type is (C_User|C_System|C_ExternCSystem), stored as SrcMgr::CharacteristicKind. Literally just the file type, no cap.

Example, bestie โ€” look at how clean this is:

- Callback: FileSkipped
  ParentFile: "/path/filename.h"
  FilenameTok: "filename.h"
  FileType: C_User

๐Ÿฆ‹ InclusionDirective Callback

Okay so InclusionDirective fires when an inclusion directive of any kind โ€” think #include, #import, etc. โ€” has been processed โœจ And the iconique part? It gets called regardless of whether the inclusion will actually result in an inclusion. So like, the directive got processed, periodt. Whether anything actually gets pulled in is a whole separate story ๐Ÿ™ˆ Not me stressing about the difference โ€” but fr fr, that distinction matters, so clock it ๐Ÿซถ

okay so hereโ€™s the argument tea for that inclusion directive callback โ€” literally every single one, no skips, bestie ๐Ÿ’…

Argument descriptions:

  • HashLoc โ€” value syntax: "(file):(line):(col)"; Clang C++ type: SourceLocation; description: the location of the '#' that starts the inclusion directive. iconic.
  • IncludeTok โ€” value syntax: (token); Clang C++ type: const Token; description: the token that indicates the kind of inclusion directive, e.g. 'include' or 'import'.
  • FileName โ€” value syntax: "(file)"; Clang C++ type: StringRef; description: the name of the file being included, as written in the source code.
  • IsAngled โ€” value syntax: (true|false); Clang C++ type: bool; description: whether the file name was enclosed in angle brackets; otherwise, it was enclosed in quotes.
  • FilenameRange โ€” value syntax: "(file)"; Clang C++ type: CharSourceRange; description: the character range of the quotes or angle brackets for the written file name.
  • File โ€” value syntax: "(file)"; Clang C++ type: const FileEntry; description: the actual file that may be included by this inclusion directive.
  • SearchPath โ€” value syntax: "(path)"; Clang C++ type: StringRef; description: contains the search path which was used to find the file in the file system.
  • RelativePath โ€” value syntax: "(path)"; Clang C++ type: StringRef; description: the path relative to SearchPath, at which the include file was found.
  • Imported โ€” value syntax: ((module name)|(null)); Clang C++ type: const Module; description: the module, whenever an inclusion directive was automatically turned into a module import or null otherwise.

Example::

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 ๐ŸŒˆ

okay so moduleImport is called when there was an explicit module-import syntax. periodt.

Argument descriptions:

  • ImportLoc โ€” value syntax: "(file):(line):(col)"; Clang C++ type: SourceLocation; description: the location of import directive token.
  • Path โ€” value syntax: "(path)"; Clang C++ type: ModuleIdPath; description: the identifiers (and their locations) of the module "path".
  • Imported โ€” value syntax: ((module name)|(null)); Clang C++ type: const Module; description: the imported module; can be null if importing failed.

Example::

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 โ”‚ โ”‚ โ”‚

okay so the last import line still reads Imported: Level2B โ€” noted, logged, moving on ๐Ÿ’…


EndOfMainFile Callback

EndOfMainFile gets called when the end of the main file is reached. That's it, that's the whole vibe โ€” it's the "we're so done here" callback ๐ŸŽ€

Argument descriptions:

  • (no arguments) โ€” literally zero, it takes nothing, it needs nothing, it's giving minimalism ๐Ÿ‘‘

Example::

- Callback: EndOfMainFile

Ident Callback

Ident gets called when a #ident or #sccs directive is read. bestie if you've got old-school source files with those sprinkled in, this is your moment โœจ

Argument descriptions:

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation, and it's the location of the directive ๐Ÿ“
  • str โ€” value syntax (name), Clang C++ type const std::string, and it's 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$"

like, look at that path, it's a whole Windows situation, D:/ and everything, iconic ๐Ÿ˜ญ


PragmaDirective Callback

PragmaDirective gets called when it starts reading any pragma directive. any. all of them. it does not discriminate ๐Ÿฆ‹

Argument descriptions:

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation, and it's the location of the directive ๐Ÿ“
  • Introducer โ€” value syntax (PIK_HashPragma|PIK__Pragma|PIK___pragma), Clang C++ type PragmaIntroducerKind, and it's the type of the pragma directive ๐Ÿ”

so yeah, Introducer tells you which flavour of pragma you're looking at โ€” PIK_HashPragma or PIK__Pragma or PIK___pragma โ€” and Loc tells you where it happened. two fields, all the tea โ˜•

โ€ฆso picking right back up at the example row that was chilling at the bottom of that table โ€” here's literally what it looks like, bestie ๐Ÿซถ:

- Callback: PragmaDirective
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Introducer: PIK_HashPragma

๐Ÿ’… PragmaComment Callback

Okay so PragmaComment is called when a #pragma comment directive is read. That's it, that's the vibe โœจ

Argument descriptions โ€” buckle up, there's three:

  • Loc โ€” value syntax is "(file):(line):(col)", Clang C++ type is SourceLocation โ€” it's giving the location of the directive ๐Ÿ“
  • Kind โ€” value syntax is ((name)|(null)), Clang C++ type is const IdentifierInfo โ€” this one's the comment kind symbol ๐Ÿ”
  • Str โ€” value syntax is (message directive), Clang C++ type is const std::string โ€” and this cutie is the comment message directive ๐Ÿ’ฌ

Example โ€” and yes, the path is that whole long Windows-looking situation, keep it exact fr fr:

- 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

Next up: PragmaDetectMismatch is called when a #pragma detect_mismatch directive is read. No cap, that's the whole description ๐ŸŽ€

Argument descriptions:

  • Loc โ€” syntax "(file):(line):(col)", type SourceLocation โ€” the location of the directive ๐Ÿ“
  • Name โ€” syntax "(name)", type const std::string โ€” literally just the name ๐Ÿท๏ธ
  • Value โ€” syntax (string), type const std::string โ€” and this one's the value ๐Ÿ’ซ

Example:

- Callback: PragmaDetectMismatch
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Name: name
  Value: value

๐Ÿงš PragmaDebug Callback

PragmaDebug is called when a #pragma clang __debug directive is read. Clang staying mysterious while debugging, iconic honestly ๐Ÿ”ฅ

Argument descriptions:

  • Loc โ€” syntax "(file):(line):(col)", type SourceLocation โ€” the location of the directive ๐Ÿ“
  • DebugType โ€” syntax (string), type StringRef โ€” this one indicates type of debug message ๐Ÿ›

Example:

- Callback: PragmaDebug
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  DebugType: warning

๐ŸŒธ PragmaMessage Callback

And then there's PragmaMessage, which is called when a #pragma message directive is read โ€” you know, the one where the compiler gossips at you in the output ๐Ÿ’ฌ

Argument descriptions โ€” and listen, the table is still going, so we're starting the list and it keeps rolling:

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation โ€” the location of the directive ๐Ÿ“

โ€ฆand we're picking that list right back up in the next bit, so don't go anywhere ๐Ÿ’–

...and the rest of the PragmaMessage arg list, bestie, since that table just kept going:

  • Namespace โ€” value syntax (name) โ€” Clang C++ type StringRef โ€” it's the namespace of the message directive. โœจ
  • Kind โ€” value syntax (PMK_Message|PMK_Warning|PMK_Error) โ€” Clang C++ type PPCallbacks::PragmaMessageKind โ€” the type of the message directive. three flavors, pick your fighter ๐Ÿ’…
  • Str โ€” value syntax (string) โ€” Clang C++ type StringRef โ€” the actual text of the message directive. the words!! ๐Ÿ“

And the example so you can see it in the wild:

- 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 gets called when a #pragma gcc diagnostic push directive is read. that's it, that's the whole vibe โ€” it's the "save my diagnostic state rn" callback ๐Ÿ’พ

Argument descriptions, because of course there are arguments:

  • Loc โ€” value syntax "(file):(line):(col)" โ€” Clang C++ type SourceLocation โ€” the location of the directive. where it happened ๐Ÿ“
  • Namespace โ€” value syntax (name) โ€” Clang C++ type StringRef โ€” namespace name.

Example, fresh off the press:

- Callback: PragmaDiagnosticPush
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Namespace: "GCC"

PragmaDiagnosticPop Callback ๐Ÿ˜ญ

PragmaDiagnosticPop is called when a #pragma gcc diagnostic pop directive is read. it's the push's other half โ€” the "ok restore everything, we're done here" callback ๐ŸŽ€

Its argument descriptions:

  • Loc โ€” value syntax "(file):(line):(col)" โ€” Clang C++ type SourceLocation โ€” the location of the directive.
  • Namespace โ€” value syntax (name) โ€” Clang C++ type StringRef โ€” namespace name.

Example, same file, same line, different callback:

- Callback: PragmaDiagnosticPop
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Namespace: "GCC"
btw notice how push and pop take the exact same two arguments โ€” Loc and Namespace. no cap, that's not a typo, that's just the shape of it ๐Ÿ’…

PragmaDiagnostic Callback ๐Ÿ”ฅ

PragmaDiagnostic is called when a #pragma gcc diagnostic directive is read. yes the plain one, no push, no pop, just the diagnostic directive itself doing its main character thing ๐Ÿ‘‘

Argument descriptions โ€” and this one has four, she's got range:

  • Loc โ€” value syntax "(file):(line):(col)" โ€” Clang C++ type SourceLocation โ€” the location of the directive.
  • Namespace โ€” value syntax (name) โ€” Clang C++ type StringRef โ€” namespace name.
  • mapping โ€” value syntax (0|MAP_IGNORE|MAP_WARNING|MAP_ERROR|MAP_FATAL) โ€” Clang C++ type diag::Severity โ€” mapping type. that's the whole severity spectrum right there, from "ignore it bestie" all the way to "fatal" ๐Ÿ’€
  • Str โ€” value syntax (string) โ€” Clang C++ type StringRef โ€” warning/error name.

โœจ and that's the diagnostic trio wrapped up โ€” the push, the pop, and the plain one, all accounted for, no facts left behind ๐Ÿฆ‹

โ€ฆand the example for that last callback wraps all the way up like this, bestie:

- Callback: PragmaDiagnostic
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Namespace: "GCC"
  mapping: MAP_WARNING
  Str: WarningName

๐Ÿงš PragmaOpenCLExtension Callback

PragmaOpenCLExtension fires when an OpenCL extension is either disabled or enabled with a pragma. That's the whole vibe check, no cap.

Argument descriptions:

  • NameLoc โ€” value syntax: "(file):(line):(col)", Clang C++ type: SourceLocation. The location of the name. ๐Ÿ“
  • Name โ€” value syntax: (name), Clang C++ type: const IdentifierInfo. Name symbol.
  • StateLoc โ€” value syntax: "(file):(line):(col)", Clang C++ type: SourceLocation. The location of the state. ๐Ÿ“
  • State โ€” value syntax: (1|0), Clang C++ type: unsigned. Enabled/disabled state. Literally the on/off switch energy.

Example:

- Callback: PragmaOpenCLExtension
  NameLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:10"
  Name: Name
  StateLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:18"
  State: 1

๐Ÿฆ‹ PragmaWarning Callback

PragmaWarning is called when a #pragma warning directive is read. She's paying attention, okay? ๐Ÿ‘€

Argument descriptions:

  • Loc โ€” value syntax: "(file):(line):(col)", Clang C++ type: SourceLocation. The location of the directive.
  • WarningSpec โ€” value syntax: (string), Clang C++ type: StringRef. The warning specifier. That's the actual instruction, iyk what I mean.
  • Ids โ€” value syntax: [(number)[, ...]], Clang C++ type: ArrayRef<int>. The warning numbers. A whole little squad of them.

Example:

- Callback: PragmaWarning
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  WarningSpec: disable
  Ids: 1,2,3

๐ŸŒธ PragmaWarningPush Callback

PragmaWarningPush is called when a #pragma warning(push) directive is read. It's giving "save my warning state for later" main character energy. ๐ŸŽ€

Argument descriptions:

  • Loc โ€” value syntax: "(file):(line):(col)", Clang C++ type: SourceLocation. The location of the directive.
  • Level โ€” value syntax: (number), Clang C++ type: int. Warning level. How high are we stacking it, fr fr.

Example:

- Callback: PragmaWarningPush
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Level: 1

๐Ÿ’œ PragmaWarningPop Callback

PragmaWarningPop is called when a #pragma warning(pop) directive is read. The push's bestie โ€” she comes back for the state, iconic behavior tbh. ๐Ÿ’…

Argument descriptions:

  • Loc โ€” value syntax: "(file):(line):(col)", Clang C++ type: SourceLocation. The location of the directive.
- Callback: PragmaWarningPop
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"

โ€ฆand that's the last row of that example, bestie โ€” breathe, we made it out of the table ๐Ÿซถ


๐ŸŒธ MacroExpands Callback

MacroExpands gets called when ::HandleMacroExpandedIdentifier when a macro invocation is found. (Yes, that's the wording, and yes, it's a whole vibe โ€” two "when"s, zero notes ๐Ÿ’€)

Argument descriptions:

  • 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.
  • Range โ€” value syntax ["(file):(line):(col)", "(file):(line):(col)"], Clang C++ type SourceRange. The source range for the expansion.
  • Args โ€” value syntax [(name)|(number)|<(token name)>[, ...]], Clang C++ type const MacroArgs. The argument tokens. Names and numbers are literal, everything else is of the form '<' tokenName '>'.

Example::

- Callback: MacroExpands
  MacroNameTok: X_IMPL
  MacroDirective: MD_Define
  Range: [(nonfile), (nonfile)]
  Args: [a <plus> y, b]

Like, look at that Args line โ€” a <plus> y, b โ€” it's literally telling you the + got tokenized into <plus>, iconic โœจ


๐Ÿฆ‹ MacroDefined Callback

MacroDefined is called when a macro definition is seen. Simple as that, no cap ๐Ÿ’…

Argument descriptions:

  • 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: MacroDefined
  MacroNameTok: X_IMPL
  MacroDirective: MD_Define

๐ŸŽ€ MacroUndefined Callback

MacroUndefined is called when a macro #undef is seen. The "it's over" moment of the preprocessor world ๐Ÿ˜ญ

Argument descriptions:

  • 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: MacroUndefined
  MacroNameTok: X_IMPL
  MacroDirective: MD_Define

(Yeah, the example says MD_Define even under MacroUndefined โ€” that's what the page says, so that's what you get, fr fr ๐ŸŒˆ)


๐Ÿ‘‘ Defined Callback

Defined is called when the 'defined' operator is seen.

Argument descriptions โ€” the table's got these columns: Argument Name, Argument Value Syntax, Clang C++ Type, Description, and the rows start withโ€ฆ

  • MacroNameTok โ€” type is (token), declared const Token โ€” this one's just the macro name token, the actual little name-y bit bestie ๐Ÿ’…
  • MacroDirective โ€” type is (MD_Define|MD_Undefine|MD_Visibility), declared const MacroDirective โ€” it's the kind of macro directive, straight out of the MacroDirective structure โœจ so like, define, undefine, or visibility, pick your fighter ๐ŸŽ€
  • Range โ€” type is ["(file):(line):(col)", "(file):(line):(col)"], declared SourceRange โ€” the source range for the directive, i.e. where it starts and where it ends, both in that (file):(line):(col) shape ๐Ÿฆ‹ lowkey the most useful field when you're tryna point at exactly where something happened

okay so those three are the rest of that struct's field list โ€” no cap, that's the whole vibe check on this chunk ๐ŸŒธ the Range one especially is giving "I will tell you exactly where to look" main character energy ๐Ÿ‘‘

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

okay so If gets called when an #if is seen โ€” that's literally the whole vibe bestie โœจ

Argument descriptions:

  • Loc โ€” value syntax is "(file):(line):(col)", Clang C++ type SourceLocation, and it's the location of the directive. ๐Ÿ“
  • ConditionRange โ€” value syntax is ["(file):(line):(col)", "(file):(line):(col)"], type SourceRange, and yep it's the source range for the condition. ๐ŸŽฏ
  • ConditionValue โ€” value syntax (true|false), type bool, and it's 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 is called when an #elif is seen โ€” the "wait actually, what if" of the preprocessor, no cap ๐Ÿ˜ญ

Argument descriptions:

  • Loc โ€” "(file):(line):(col)", type SourceLocation, the location of the directive. ๐Ÿ“
  • ConditionRange โ€” ["(file):(line):(col)", "(file):(line):(col)"], type SourceRange, the source range for the condition. ๐ŸŽฏ
  • ConditionValue โ€” (true|false), type bool, the condition value. ๐Ÿ’…
  • IfLoc โ€” "(file):(line):(col)", type SourceLocation, the location of the directive. ๐Ÿ“
๐Ÿ‘€ yup, Loc and IfLoc both literally say "The location of the directive" in the source doc โ€” I'm not paraphrasing, that's just what it says, fr fr.

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 โ€” yes queen, the classic "is this even defined" check ๐Ÿ‘‘

Argument descriptions:

  • Loc โ€” value syntax is "(file):(line):(col)", Clang C++ type is SourceLocation ๐Ÿ“ โ€” it's giving "where exactly did this directive live", and that's literally what it is: the location of the directive. Slay.
  • MacroNameTok โ€” value syntax is (token), Clang C++ type is const Token ๐ŸŽ€ โ€” the macro name token, bestie. That's it, that's the whole vibe.
  • MacroDirective โ€” value syntax is (MD_Define|MD_Undefine|MD_Visibility), Clang C++ type is const MacroDirective ๐Ÿ‘‘ โ€” this one's the kind of macro directive, straight from the MacroDirective structure. Three options, no cap, pick one.

๐Ÿ’… example, because we love a visual

Example::

  Callback: Ifdef
    Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-conditional.cpp:3:1"
    MacroNameTok: MACRO
    MacroDirective: MD_Define

See how it all comes together? Ifdef fires, and it hands you the Loc, the MacroNameTok: MACRO, and the MacroDirective: MD_Define. Iconic behaviour, honestly. ๐Ÿฆ‹


Ifndef Callback ๐ŸŒธ

Ifndef is called when an #ifndef is seen โœจ โ€” yes, the mirror-image twin of the one above, and it's just as much of a main character.

The arguments, described (yes, we're doing this again, and no, I'm not sorry):

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation ๐Ÿ“ โ€” the location of the directive. Same deal as before, still essential.
  • MacroNameTok โ€” value syntax (token), Clang C++ type is const Token ๐ŸŽ€ โ€” the macro name token. Your fave little token, back for round two.
  • MacroDirective โ€” value syntax is (MD_Define|MD_Undefine|MD_Visibility), Clang C++ type is const MacroDirective ๐Ÿ”ฅ โ€” the kind of macro directive from the MacroDirective structure. Choices, choices.
ngl, if you clocked the Ifdef table a second ago, this is the exact same trio of arguments โ€” same names, same value syntaxes, same C++ types, same descriptions. fr fr, no surprises here, and that's the point ๐Ÿ’–
- 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 ๐Ÿฆ‹

Okay so Else gets called when an #else is seen. That's it, that's the vibe check. ๐Ÿ’…

Argument descriptions โ€” bestie, here's the tea on what gets passed in:

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation โ€” the location of the else directive. ๐Ÿ“
  • IfLoc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation โ€” the location of the if directive. Like, it remembers where the whole thing started, iconic. ๐ŸŽ€

Example โ€” no cap, this is literally what it prints:

- 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 ๐ŸŒธ

And Endif fires when an #endif is seen. Closing the block, main character energy fr fr. โœจ

Argument descriptions:

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation โ€” the location of the endif directive. ๐Ÿ“
  • IfLoc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation โ€” the location of the if directive. Again, keeps the receipts of where the if was. ๐Ÿงš

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 it from source? Say less, here's the roadmap bestie:

  1. Go read Getting Started with the LLVM System and Clang Tools Documentation โ€” they've got all the info on getting sources for LLVM, Clang, and Clang Extra Tools. ๐Ÿ“š
  2. Getting Started with the LLVM System and Building LLVM with CMake give you the directions for how to build. And like, if all your sources are checked out into the right place, the LLVM build will build Clang Extra Tools and their dependencies automatically. Slay, it just does it. ๐Ÿซถ
  • ๐Ÿ’ก If you're using CMake, you can also use the pp-trace target to build just the pp-trace tool and its dependencies. So efficient, we love to see it.

๐ŸŒˆ 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. Literally turning your comments into docs, iykyk. ๐Ÿ“โœจ

Bestie, real talk: the tool is in a very early development stage, so you might encounter bugs and crashes. ๐Ÿ˜ญ It's giving "work in progress" and that's okay! Submitting reports with information about how to reproduce the issue to the LLVM bug tracker will definitely help the project. And if you have any ideas or suggestions, please to put a feature request there. (yes, keep it cute, drop that feature request ๐Ÿ’…)

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 How To Setup Tooling For LLVM). Iconic requirement, honestly. ๐ŸŽ€

The tool will process a list of files by default:

$ clang-doc File1.cpp File2.cpp ... FileN.cpp

And the tool can be also used with a compile commands database:

--executor=all-TUs with a compile_commands.json is the whole vibe:

$ clang-doc --executor=all-TUs compile_commands.json

And if you don't want the entire database (valid, honestly, sometimes you just want a lil subset), hit it with the --filter flag:

$ clang-doc --executor=all-TUs --filter=File[0-9]+.cpp compile_commands.json

๐ŸŒธ Output ๐ŸŒธ

Okay so here's the tea: clang-doc doesn't give you one big blob โ€” it produces a directory of documentation. Like, a whole lil ecosystem. One file gets produced for each namespace and record in the project source code, and each of those files contains all the documentation for that item (including the contained functions, methods, and enums). So everything that belongs to a thing lives with that thing. Organized. Iconic. ๐ŸŽ€

The top-level directory is configurable through the output flag, babes:

$ clang-doc --output=output/directory/ compile_commands.json

โœจ Configuration โœจ

Configuration for clang-doc is currently limited to command-line options. That's the whole menu, bestie. In the future it may develop the ability to use a configuration file, but no such efforts are currently in progress โ€” so don't @ me about a config file, it's not a thing yet. ๐Ÿ’…

๐Ÿ’– 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.

Now let me break the actual flags down so it's not just a wall of text, because you deserve clarity ๐Ÿซถ

  • --doxygen โ€” use only doxygen-style comments to generate docs. If it's not doxygen-flavored, it's not invited to the docs party.
  • --extra-arg=<string> โ€” an additional argument to append to the compiler command line. You can use this several times, so go off, stack 'em.
  • --extra-arg-before=<string> โ€” same energy, but this one prepends to the compiler command line. Also can be used several times.
  • --format=<value> โ€” the format for your outputted docs. Choose your fighter:
    • =yaml โ€” documentation in YAML format.
    • =md โ€” documentation in MD format.
    • =html โ€” documentation in HTML format.
  • --ignore-map-errors โ€” keep going even if files are not mapped correctly. Like, push through. ๐Ÿ’€
  • --output=<string> โ€” the directory for outputting generated files.
  • -p <string> โ€” build path.
  • --project-name=<string> โ€” name of the project.
  • --public โ€” document only public declarations. Private stuff stays private, lowkey.
  • --repository=<string> โ€” URL of the repository that hosts the code. It's used for links to definition locations.
  • --source-root=<string> โ€” the directory where processed files are stored. Links to definition locations will only be generated if the file is in this dir. No dir, no link. Periodt.
  • --stylesheets=<string> โ€” CSS stylesheets to extend the default styles.
Heads up bestie: the following flags should only be used if format is set to html: -repository, -source-root, -stylesheets. Don't come for me if you use them with yaml or md, okay? ๐Ÿ™ˆ

๐Ÿ‘‘ CLANG TOOLS EXTRA MAINTAINERS ๐Ÿ‘‘

This file is a list of the maintainers for the Extra Clang Tools project. Real ones. Let's get into it.

๐Ÿฆ‹ Table of Contents ๐Ÿฆ‹

  • Clang Tools Extra Maintainers
  • Active Maintainers
    • Lead Maintainer
    • clang-tidy
    • clang-query
    • clang-doc
    • clangd
  • Inactive Maintainers
    • Emeritus Lead Maintainers
    • Inactive component maintainers

๐ŸŒˆ ACTIVE MAINTAINERS ๐ŸŒˆ

The following people are the active maintainers for the project. Please reach out to them for code reviews, questions about their area of expertise, or other assistance. They're literally here to help, so don't be shy ๐Ÿ’œ

โญ Lead Maintainer โญ

Aaron Ballman โ€” aaron@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord)

๐ŸŒธ clang-tidy ๐ŸŒธ

Julian Schmidt โ€” git.julian.schmidt@gmail.com (email), 5chmidti (GitHub), 5chmidti (Discourse), 5chmidti (Discord)

Victor Baranov โ€” bar.victor.2002@gmail.com (email), vbvictor (GitHub), vbvictor (Discourse), vbvictor (Discord)

Victor Chernyakin โ€” chernyakin.victor.j@outlook.com (email), localspook

  • Continuing that maintainer row from the previous chunk: (GitHub), LocalSpook (Discourse), localspook (Discord)
  • Next maintainer row: Zeyi Xu โ€” zeyi2@nekoarch.cc (email), zeyi2 (Github), zeyi2 (Discourse), zeyi1 (Discord)
  • clang-query โ€” Aaron Ballman: aaron@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord)
  • clang-doc โ€” Paul Kirth: paulkirth@google.com (email), ilovepi (GitHub), ilovepi (Discourse)
  • clang-doc โ€” Erick Velez: erickvelez7@gmail.com (email), evelez7 (GitHub), evelez (Discourse), erick.velez (Discord)
  • clangd โ€” Nathan Ridge: zeratul976@hotmail.com (email), HighCommander4 (GitHub), HighCommander4 (Discourse), nridge (Discord)
  • clangd โ€” Aleksandr Platonov: arcsinx45@gmail.com (email), ArcsinX (GitHub), ArcsinX (Discourse)

๐Ÿ’€ INACTIVE MAINTAINERS ๐Ÿ’€

Okay so these absolute legends put in the time as maintainers but aren't active in that role anymore. Thank you for all your help with the success of the project! No cap, we appreciate you ๐Ÿซถ

๐ŸŒŸ Emeritus Lead Maintainers ๐ŸŒŸ

  • Manuel Klimek โ€” klimek@google.com (email), r4nt (GitHub)

๐Ÿซถ Inactive component maintainers ๐Ÿซถ

  • Nathan James โ€” n.james93@hotmail.co.uk โ€” clang-tidy
  • Julie Hockett โ€” juliehockett@google.com โ€” clang-doc
  • Sam McCall โ€” sammccall@google.com (email), sam-mccall (GitHub, Discourse, Discord) โ€” clangd
  • Peter Chou โ€” peterchou411@gmail.com (email), PeterChou1 (GitHub, Discourse), .peterchou (Discord) โ€” clang-doc
  • Piotr Zegar โ€” me@piotrzegar.pl (email), PiotrZSL (GitHub), PiotrZSL (Discourse), PiotrZSL (Discord) โ€” clang-tidy
  • Kadir ร‡etinkaya โ€” kadircet@google.com (email), kadircet (GitHub), kadircet (Discourse), kadircet (Discord) โ€” clangd
  • Chris Bieneman โ€” chris.bieneman@gmail.com (email), llvm-beanz (GitHub), beanz (Discord), beanz (Discourse) โ€” clangd
  • Congcong Cai โ€” congcongcai0907@163.com (email), HerrCai0907 (GitHub), HerrCai0907 (Discourse) โ€” clang-tidy

About the Doxygen docs, quick vibe check: the Doxygen documentation describes the internal software that makes up the tools of clang-tools-extra, not the external use of these tools. It 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.

The clickable bits are:

  • Doxygen documentation
  • Index
  • Search Page
NOTE: This documentation is generated directly from the source code with doxygen. Since the tools of clang-tools-extra are constantly under active development, what you're about to read is out of date! FR FR, it said that. ๐Ÿ˜ญ

โœจ AUTHOR โœจ

The Clang Team

2007-2026, The Clang Team


Bye bestie! ๐Ÿ’… Signing off from the EXTRACLANGTOOLS(1) page โ€” source/version clang-tools-extra / 23, dated Oct 06, 2026. Mwah! ๐Ÿ’‹๐Ÿ’–

๐Ÿ“ฆ where this page comes from