EXTRACLANGTOOLS(1)

๐Ÿ“ฆ
version 22.1.8 ยท 3 versions
, in fedora 44

okay bestie, you just cracked open extraclangtools(1) ๐Ÿ’… โ€” yes, the Extra Clang Tools documentation, the whole gorgeous little ecosystem of tools built on top of Clang's tooling APIs. Grab a strawberry matcha, we're doing this โœจ

๐ŸŽ€ NAME ๐ŸŽ€

extraclangtools โ€” Extra Clang Tools Documentation ๐Ÿฆ‹

Welcome to the clang-tools-extra project, which is literally where all the extra tools live โ€” the ones built using Clang's tooling APIs. That's the whole vibe. ๐ŸŒธ


๐Ÿ’– EXTRA CLANG TOOLS 22.1.8 RELEASE NOTES ๐Ÿ’–

Here's the map, bestie, so you don't get lost:

  • Introduction
  • What's New in Extra Clang Tools 22.1.8?
    • Major New Features
    • Potentially Breaking Changes
    • Improvements to clangd
      • Compile flags
      • Diagnostics
      • Hover
      • Cross-references
      • Inlay hints
      • Call hierarchy
      • Document symbols
      • Code folding
      • Code completion
      • Code actions
      • 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 โ€” https://llvm.org/ ๐Ÿ‘‘


๐ŸŒˆ Introduction ๐ŸŒˆ

This document is the release notes for the Extra Clang Tools, part of the Clang release 22.1.8 ๐ŸŽ‰ Here we spill the status of the Extra Clang Tools in some detail โ€” the major improvements since the last release, and all the new feature work. Every single LLVM release is downloadable from the LLVM releases web site, https://llvm.org/releases/. ๐Ÿซถ

Want more tea about Clang or LLVM, including the latest release? Peep the Clang Web Site at https://clang.llvm.org or the LLVM Web Site at https://llvm.org. ๐Ÿ’œ

heads up bestie โš ๏ธ if you're reading this file from a Git checkout or the main Clang web page, then this document applies to the next release, not the current one. To see the release notes for a specific release, go look at the releases page, https://llvm.org/releases/.

โœจ What's New in Extra Clang Tools 22.1.8? โœจ

Some of the major new features and improvements to Extra Clang Tools are listed right here. Generic improvements to Extra Clang Tools as a whole (or to its underlying infrastructure) get described first, and then we get into the tool-specific sections. slay. ๐Ÿ’…


๐Ÿ”ฅ Major New Features ๐Ÿ”ฅ

(this section is keeping it mysterious โ€” no items listed) ๐Ÿ™ˆ


๐Ÿ’€ Potentially Breaking Changes ๐Ÿ’€

  • Deprecated the clang-tidy hicpp module. All checks have been moved to other modules instead. The hicpp module will be removed for the 23.x release. ๐Ÿซ 
  • Deprecated the clang-tidy zircon module. All checks have been moved to the fuchsia module instead. The zircon module will be removed in the 24th release.
  • Removed clang-tidy's global options IgnoreMacros and StrictMode, which were documented as deprecated since clang-tidy-20. Users should use the check-specific options of the same name instead. no cap, they're gone ๐Ÿšซ
  • Removed clang-analyzer-* checks from default checks in clang-tidy. From now on, users should specify explicitly that they want CSA checks to run in clang-tidy via clang-analyzer-*.
  • Renamed a few clang-tidy check options, as they were misspelled:
    • NamePrefixSuffixSilenceDissimilarityTreshold to NamePrefixSuffixSilenceDissimilarityThreshold in bugprone-easily-swappable-parameters <>
    • CharTypdefsToIgnore to CharTypedefsToIgnore in bugprone-signed-char-misuse <>
  • Modified the custom message format of bugprone-unsafe-functions <> by assigning a special meaning to the character > at the start of the value of the option CustomFunctions. If the option value starts with >, then the replacement suggestion part of the message (which would be included by default) is omitted. (This does not change the warning locations.) โš ๏ธ
  • clang-tidy now displays warnings from all non-system headers by default. Previously, users had to explicitly opt-in to header warnings using -header-filter='.*'. To disable warnings from non-system, set -header-filter to an empty string. ๐Ÿ‘€
  • The clang-tidy/ClangTidyModuleRegistry.h header has been deprecated and will be removed in LLVM 24. All of the symbols it used to define have been moved into clang-tidy/ClangTidyModule.h.

๐Ÿฆ‹ Improvements to clangd ๐Ÿฆ‹

๐ŸŒธ Compile flags ๐ŸŒธ

  • Added proper support for the -std=c++latest flag, often used by Windows users writing C++23 code. icon behavior fr fr

๐Ÿ’… Diagnostics ๐Ÿ’…

  • Fixed a bug that caused the quick-fix for the readability-identifier-naming diagnostic not to work in some editors. we love a bug fix ๐Ÿซถ

โœจ Hover โœจ

  • Added support for parsing and presenting doxygen and markdown documentation ๐Ÿ“–
  • Added a MacroContentsLimit config option to allow users to opt into showing macro definitions in hovers even if they're very long
  • Fixed a bug that prevented documentation from being shown for members of class templates defined in a header
  • Fixed a bug that prevented documentation from being shown for abbreviated function templates

๐ŸŽ€ Cross-references ๐ŸŽ€

  • Improved "go to definition" support for dependent auto
  • Improved "go to definition" support for C++23 "deducing this"
  • Fixed a bug where attributes on a method broke various features including "go to definition" on a method's name
  • "Find references" on a constructor now finds call sites of forwarding functions such as make_unique as well
  • "Document links" now supports #include directives with a macro argument

๐ŸŒˆ Inlay hints ๐ŸŒˆ

  • Show designator hints for aggregate initialization of structures with parentheses-list initialization (CXXParenListInitExpr) syntax.
  • Show type hints for simple cases of dependent auto

๐Ÿ‘‘ Call hierarchy ๐Ÿ‘‘

  • Support invoking call hierarchy on enum constants
  • When invoked on a virtual function, show callers of base functions as well

๐Ÿ“ Document symbols ๐Ÿ“

  • Include inline friend functions in document symbols

๐Ÿงš Code folding ๐Ÿงš

  • Support folding of preprocessor branches
  • Support folding of #pragma region blocks

โญ Code completion โญ

  • Added a new MacroFilter configuration option to Completion to allow fuzzy-matching with the FuzzyMatch option when suggesting macros. ExactPrefix is the default, which retains previous behavior of suggesting macros which match the prefix exactly.
  • Improved support for C++23 "deducing this"
  • Header insertion now checks AngledHeaders and QuotedHeaders against resolved rather than spelled paths, consistent with the existing behaviour of include-cleaner diagnostics.

๐Ÿ’– Code actions ๐Ÿ’–

  • New Override pure virtual methods code action. When invoked on a class definition, this action automatically generates C++ override declarations for all pure virtual methods inherited from its base classes that have not yet been implemented. The generated method stubs prompts the user for the actual implementation. The overrides are intelligently grouped under their original access specifiers (e.g., public, protected), creating new access specifier blocks if necessary. main character energy honestly ๐Ÿ‘‘
  • Improved the "move function body out-of-line" code action's logic for choosing where to place the definition. It now tries to place it next to the definition of a method with an adjacent declaration.

๐Ÿซถ C++20 Modules support ๐Ÿซถ

  • Fixed a crash when using --experimental-modules-support without a compilation database ๐Ÿ’€
  • Added a --debug-modules-builder option to help debug clangd's modules support
  • Improved diagnostics related to modules
  • Clangd now tries to reuse a module from the project's build if it's suitable

๐Ÿ™ˆ Miscellaneous ๐Ÿ™ˆ

  • Add wildcard .gitignore file to the clangd index directory.

๐Ÿ’œ Improvements to clang-doc ๐Ÿ’œ

  • Use the Mustache HTML generator as the default HTML option.
  • Document concepts, typedefs, aliases, public class members, nested records, nested namespaces, global functions, global variables, protected methods and members and friends in HTML.
  • Document throws comments in HTML.
  • Add definition location for classes in HTML.
  • Show class template declaration in HTML.
  • Add a top-level index page in HTML. This page lists the top-level namespaces and serves as the documentation's root URL.
  • Add a Home button to the navigation bar which replaces non-useful buttons. This new button links to the top-level index page.
  • Add a breadcrumb navigation bar to the navbar. This helps navigate through a documented entity's context.
  • If a filename is too long, then the name will be truncated and the documented entity's USR will be concatenated to guarantee its uniqueness.
  • Revert the flat directory structure to a nested directory structure.

๐Ÿฆ„ Improvements to clang-query ๐Ÿฆ„

  • Matcher queries interpreted by clang-query are now support trailing comma (,) in matcher arguments. Note that C++ still doesn't allow this in function arguments. So when porting a query to C++, remove all instances of trailing comma (otherwise C++ compiler will just complain about "expected expression"). lowkey important bestie โš ๏ธ

๐ŸŒŸ Improvements to clang-tidy ๐ŸŒŸ

  • The check_clang_tidy.py tool now recognizes the -std argument when run over C files. If -std is not specified, it defaults to c99-or-later.
  • clang-tidy now displays warnings from all non-system headers by default. Previously, users had to explicitly opt-in to header warnings using -header-filter='.*'. To disable warnings from non-system, set -header-filter to an empty string.
  • clang-tidy no longer attempts to analyze code from system headers by default, greatly improving performance. This behavior is disabled if the SystemHeaders option is enabled. fast AND iconic ๐Ÿƒโ€โ™€๏ธ
  • clang-tidy now supports query based custom checks by CustomChecks configuration option. Query Based Custom Check Document <>
  • The run-clang-tidy.py and clang-tidy-diff.py scripts now run checks in parallel by default using all available hardware threads. Both scripts display the number of threads being used in their output.
  • Improved run-clang-tidy.py by adding a new option enable-check-profile to enable per-check timing profiles and print a report based on all analyzed files.
  • Improved documentation of the -line-filter command-line flag of clang-tidy and run-clang-tidy.py.
  • Improved clang-tidy option -quiet by suppressing diagnostic count messages.
  • Improved clang-tidy by not crashing when an empty directory field is used in a compilation database; the current working directory will be used instead, and an error message will be printed. ๐Ÿซถ
  • Removed clang-tidy's global options IgnoreMacros and StrictMode, which were documented as deprecated since clang-tidy-20. Users should use the check-specific options of the same name instead.
  • โœจ Improved run-clang-tidy.py and clang-tidy-diff.py scripts by adding the -hide-progress option, so you can mute all those progress and informational messages. Quiet queen behavior ๐Ÿ’…
  • โœจ Removed clang-analyzer-* check from the default checks in clang-tidy. Bestie, from now on you have to explicitly say you want CSA checks to run in clang-tidy. No more sneaky defaults ๐Ÿซถ
  • โœจ Improved clang-tidy by adding the --removed-arg option, which removes arguments sent to the compiler when invoking Clang-Tidy. This option also got added to run-clang-tidy.py and clang-tidy-diff.py, and you can configure it in the config file through the RemovedArgs option. One option, three places, we love consistency โœจ
  • ๐Ÿšจ Deprecated the clang-tidy zircon module. All the checks moved over to the fuchsia module instead. The zircon module is getting yeeted in the 24th release. Rip bozo, we hardly knew ye ๐Ÿ’€
  • โœจ Improved clang-tidy configuration parsing by allowing the same list syntax in WarningsAsErrors as in Checks. Equality, finally ๐Ÿ˜Œ

๐ŸŒธ New checks ๐ŸŒธ

  • ๐ŸŒŸ New abseil-unchecked-statusor-access check. Finds uses of absl::StatusOr without checking if a value is present. Like, ma'am, did you even look inside first?? ๐Ÿ˜ญ
  • ๐ŸŒŸ New bugprone-derived-method-shadowing-base-method check. Finds derived class methods that shadow a (non-virtual) base class method. Shady behavior fr fr ๐Ÿ•ถ๏ธ
  • ๐ŸŒŸ New bugprone-invalid-enum-default-initialization check. Detects default initialization (to 0) of variables with enum type where the enum has no enumerator with a value of 0. So you're defaulting to a value that literally does not exist. Iconic, honestly ๐Ÿ’€
  • ๐ŸŒŸ New cppcoreguidelines-pro-bounds-avoid-unchecked-container-access check. Finds calls to operator[] in STL containers and suggests replacing them with safe alternatives. Safety first, always ๐Ÿฆ‹
  • ๐ŸŒŸ New google-runtime-float check. Finds uses of long double and suggests against their use due to lack of portability. It's giving "works on my machine" energy ๐Ÿ™ˆ
  • ๐ŸŒŸ New llvm-mlir-op-builder check. Checks for uses of MLIR's old / to-be-deprecated OpBuilder::create<T> form and suggests using T::create instead. Out with the old โœจ
  • ๐ŸŒŸ New llvm-use-ranges check. Finds calls to STL library iterator algorithms that could be replaced with LLVM range-based algorithms from llvm/ADT/STLExtras.h. Glow up your loops ๐Ÿ’…
  • ๐ŸŒŸ New misc-override-with-different-visibility check. Finds virtual function overrides with different visibility than the function in the base class. Why are you hiding ๐Ÿ‘€
  • ๐ŸŒŸ New readability-inconsistent-ifelse-braces check. Detects if/else statements where one branch uses braces and the other does not. Pick a lane, bestie ๐Ÿ˜ค
  • ๐ŸŒŸ New readability-redundant-parentheses check. Detect redundant parentheses. ((((yes)))) ๐Ÿ’€
  • ๐ŸŒŸ New readability-redundant-typename check. Finds redundant uses of the typename keyword. You're doing too much, chill ๐ŸŽ€

๐Ÿ‘‘ New check aliases ๐Ÿ‘‘

  • ๐Ÿ” Renamed cert-dcl50-cpp to modernize-avoid-variadic-functions, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-dcl58-cpp to bugprone-std-namespace-modification, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-env33-c to bugprone-command-processor, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-err34-c to bugprone-unchecked-string-to-number-conversion, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-err52-cpp to modernize-avoid-setjmp-longjmp, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-err58-cpp to bugprone-throwing-static-initialization, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-err60-cpp to bugprone-exception-copy-constructor-throws.
  • ๐Ÿ” Renamed cert-flp30-c to bugprone-float-loop-counter, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-mem57-cpp to bugprone-default-operator-new-on-overaligned-type, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-msc30-c to misc-predictable-rand, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-msc32-c to bugprone-random-generator-seed, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-msc50-cpp to misc-predictable-rand, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-msc51-cpp to bugprone-random-generator-seed, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-oop57-cpp to bugprone-raw-memory-call-on-non-trivial-type, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed cert-oop58-cpp to bugprone-copy-constructor-mutates-argument, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed fuchsia-multiple-inheritance to misc-multiple-inheritance, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed google-build-namespaces to misc-anonymous-namespace-in-header, keeping the initial check as an alias to the new one.
  • ๐Ÿ” Renamed google-readability-casting to modernize-avoid-c-style-cast, keeping the initial check as an alias to the new one.

๐Ÿฆ‹ Changes in existing checks ๐Ÿฆ‹

  • ๐Ÿ’– Improved bugprone-chained-comparison check by adding a new option IgnoreMacros to suppress warnings within macro expansions.
  • ๐Ÿ’– Improved bugprone-dynamic-static-initializers check:
    • Avoided false positives for constexpr variables whose initializers are value-dependent.
    • Added support for C++20 constinit variables. C++20 stans rise up ๐ŸŒˆ
  • ๐Ÿ’– Improved bugprone-easily-swappable-parameters check by correcting a spelling mistake on its option NamePrefixSuffixSilenceDissimilarityTreshold. (Yes, that's the spelling. Yes, it's the whole point. No, I will not fix it for you ๐Ÿ’…)
  • ๐Ÿ’– Improved bugprone-exception-escape check:
    • Exceptions from lambda captures are now diagnosed, exceptions in the bodies of lambdas that aren't actually invoked are not.
    • Fixed an issue where the check wouldn't diagnose throws in arguments to functions or constructors.
    • Added fine-grained configuration via options CheckDestructors, CheckMoveMemberFunctions, CheckMain, CheckedSwapFunctions, and CheckNothrowFunctions. So many knobs, we're obsessed ๐ŸŽ€
  • ๐Ÿ’– Improved bugprone-infinite-loop check by adding detection for variables introduced by structured bindings.
  • ๐Ÿ’– Improved bugprone-invalid-enum-default-initialization with new IgnoredEnums option to ignore specified enums during analysis.
  • ๐Ÿ’– Improved bugprone-macro-parentheses check by fixing false positives when using C++ template parameters.
  • ๐Ÿ’– Improved bugprone-narrowing-conversions check by fixing false positive from analysis of a conditional expression in C.
  • ๐Ÿ’– Improved bugprone-not-null-terminated-result check by fixing bogus fix-its for strncmp and wcsncmp on Windows and a crash caused by certain value-dependent expressions. (Crash fixed = slay โœจ)
  • ๐Ÿ’– Improved bugprone-reserved-identifier check by ignoring declarations and macros in system headers. Not our problem, not our warning ๐Ÿ˜Œ
  • ๐Ÿ’– Improved bugprone-signed-char-misuse check by fixing false positives on C23 enums with the fixed underlying type of signed char.
  • ๐Ÿ’– Improved bugprone-sizeof-expression check by fixing a crash on sizeof of an array of dependent type.
  • ๐Ÿ’– Improved bugprone-suspicious-include check by adding IgnoredRegex option.
  • ๐Ÿ’– Improved bugprone-tagged-union-member-count by fixing a false positive when enums or unions from system header files or the std namespace are treated as the tag or the data part of a user-defined tagged union respectively.
  • ๐Ÿ’– Improved bugprone-throw-keyword-missing check:
    • Now it only considers the canonical types of base classes as written.
    • Added a note on the base class that triggered the warning.
    • Fixed an issue where the check wouldn't fire in constructors or (in certain contexts) lambdas.
  • ๐Ÿ’– Improved bugprone-unchecked-optional-access check:
    • Added support for NullableValue::makeValue and NullableValue::makeValueInplace to prevent false-positives for BloombergLP::bdlb::NullableValue.
    • Fixed false-positives for bsl::optional containing allocator-aware type.
    • Added the IgnoreValueCalls option to suppress diagnostics for optional::value().
    • Added the IgnoreSmartPointerDereference option to ignore optionals reached via smart-pointer-like dereference, while still diagnosing UB-prone dereferences via operator* and operator->.
  • ๐Ÿ’– Improved bugprone-unhandled-self-assignment check by adding an additional matcher that generalizes the copy-and-swap idiom pattern detection.
  • ๐Ÿ’– Improved bugprone-unsafe-functions check by hiding the default suffix when the reason starts with the character > in the CustomFunctions option.
  • ๐Ÿ’– Improved bugprone-use-after-move check:
    • Added InvalidationFunctions option to support custom invalidation functions.
    • Added ReinitializationFunctions option to support custom reinitialization functions.
  • ๐Ÿ’– Improved cppcoreguidelines-avoid-non-const-global-variables check by adding a new option AllowThreadLocal that suppresses warnings on non-const global variables with thread-local storage duration.
  • ๐Ÿ’– Improved cppcoreguidelines-init-variables check by fixing the insertion location for function pointers with multiple parameters.
  • ๐Ÿ’– Improved cppcoreguidelines-macro-usage check by excluding macro bodies that starts with __attribute__((..)) keyword. Such a macro body is unlikely a proper expression and so suggesting users an impossible rewrite into a template function should be avoided.
  • ๐Ÿ’– Improved cppcoreguidelines-prefer-member-initializer check to avoid false positives on inherited members in class templates.
  • ๐Ÿ’– Improved cppcoreguidelines-pro-bounds-pointer-arithmetic check adding AllowIncrementDecrementOperators option to allow pointer arithmetic via prefix/postfix increment or decrement operators.
  • ๐Ÿ’– Improved cppcoreguidelines-pro-type-member-init check to correctly ignore std::array and other array-like containers when IgnoreArrays option is set to true.
  • ๐Ÿ’– Improved fuchsia-multiple-inheritance by fixing an issue where the check would only analyze the first class with a given name in the program, missing any subsequent classes with that same name (declared in a different scope).
  • ๐Ÿ’– Improved google-readability-casting check by adding fix-it notes for downcasts and casts to void pointer.
  • ๐Ÿ’– Improved google-readability-todo check to accept the new TODO format from the Google Style Guide.
  • ๐Ÿ’– Improved llvm-prefer-isa-or-dyn-cast-in-conditionals check:
    • Fix-it handles callees with nested-name-specifier correctly.
    • if statements with init-statement (if (auto X = ...; ...)) are handled correctly.
    • for loops are supported. Finally ๐Ÿ™Œ
  • ๐Ÿ’– Improved misc-const-correctness check:
    • Fixed false positives when pointers is transferred to non-const references.
    • Fixed false positives of function pointer, on return of non-const pointer, on pointer-to-member operator.
  • Fixed false positives when the address of a variable is taken to be passed to a function. no more getting yelled at for something you didn't even do bestie ๐Ÿ’…
  • Improved misc-coroutine-hostile-raii <> check by adding the option AllowedCallees, that allows exempting safely awaitable callees from the check. yes queen, opt out of the drama โœจ
  • Improved misc-header-include-cycle <> check performance. zoom zoom ๐Ÿฆ‹
  • Improved misc-use-internal-linkage <> check:
    • Added suggestion of internal linkage for user-defined types (structs, classes, unions, and enums). put a ring on it, internally ๐Ÿ”’
    • Added fine-grained options AnalyzeFunctions, AnalyzeVariables, AnalyzeTypes to control whether the check should diagnose functions, variables, and/or user-defined types. pick your battles fr fr
    • Enabled the check for C. C girlies rise ๐Ÿ™Œ
  • Improved modernize-avoid-c-arrays <> to not diagnose array types which are part of an implicit instantiation of a template. template girlies stay unbothered ๐Ÿ’›
  • Improved modernize-use-constraints <> check by fixing a crash on uses of non-standard enable_if with a signature different from std::enable_if (such as boost::enable_if). the crash is DEAD ๐Ÿ’€
  • Improved modernize-use-default-member-init <> check to enhance the robustness of the member initializer detection. sturdy. stable. iconic. โœจ
  • Improved modernize-use-designated-initializers <> check to suggest using designated initializers for aliased aggregate types. aliased? handled ๐Ÿ’…
  • Improved modernize-use-integer-sign-comparison <> by providing correct fix-its when the right-hand side of a comparison contains a non-C-style cast. fix-its actually fixing things, revolutionary ๐Ÿซถ
  • Improved modernize-use-nullptr <> check by fixing a crash on Windows when the check was enabled with a 32-bit clang-tidy binary. Windows girlies we got u ๐ŸชŸ
  • Improved modernize-use-override <> by fixing an issue where the check would sometimes suggest inserting override in an invalid place. NOT there bestie, there ๐Ÿ™ˆ
  • Improved modernize-use-ranges <> check to suggest using the more idiomatic std::views::reverse where it used to suggest std::ranges::reverse_view. aesthetic upgrade unlocked ๐ŸŒธ
  • Improved modernize-use-scoped-lock <> check by fixing a crash on malformed code (common when using clang-tidy through clangd). clangd girlies, I felt that ๐Ÿ˜ญ
  • Improved modernize-use-std-format <> check to correctly match when the format string is converted to a different type by an implicit constructor call. implicit constructor said "surprise!" and we said "we know" ๐Ÿ’…
  • Improved modernize-use-std-print <> check to correctly match when the format string is converted to a different type by an implicit constructor call, and fixed a crash when handling format strings containing non-ASCII characters. non-ASCII besties we see you and we love you ๐ŸŒˆ
  • Improved modernize-use-using <> check to correctly provide fix-its for typedefs of pointers or references to array types. pointers to arrays, the final boss ๐ŸŽฎ
  • Improved performance-move-const-arg <> check by avoiding false positives on trivially copyable types with a non-public copy constructor. leave them alone, they're doing great โœจ
  • Improved performance-unnecessary-copy-initialization <> by printing the type of the diagnosed variable. tell me WHO you are bestie ๐Ÿ—ฃ๏ธ
  • Improved performance-unnecessary-value-param <> by printing the type of the diagnosed variable and correctly generating fix-it hints for parameter-pack arguments. parameter packs getting the respect they deserve ๐ŸŽ€
  • Improved portability-template-virtual-member-function <> check to avoid false positives on pure virtual member functions. pure virtual = pure vibes ๐Ÿ’š
  • Improved readability-container-contains <> to support string comparisons to npos. Internal changes may cause new rare false positives in non-standard containers. rare false positives, but like... noted. โš ๏ธ
  • Improved readability-container-data-pointer <> check by correctly adding parentheses when the container expression is a dereference. parentheses: the accessory that makes the fit work ๐Ÿ’…
  • Improved readability-container-size-empty <> check by correctly generating fix-it hints when size method is called from implicit this, ignoring default constructors with user provided arguments and adding detection in container's method except empty. that's a lot of details and every one of them matters ๐Ÿซถ
  • Improved readability-duplicate-include <> check by adding the IgnoredFilesList option (semicolon-separated list of regexes or filenames) to allow intentional duplicates. sometimes you WANT the duplicate, okay?? ๐Ÿ˜ค
  • Improved readability-identifier-naming <> check:
    • Now ignores declarations and macros in system headers. system headers catching a break for once ๐Ÿ”ฅ
    • The documentation is improved to differentiate the general options from the specific ones. docs ate ๐Ÿ“š
    • Options for fine-grained control over constexpr variables were added. constexpr girlies, this one's for you โœจ
    • Added default options which simplify configs by removing the need to specify each identifier kind separately. less typing = more slaying ๐Ÿ’…
  • Improved readability-implicit-bool-conversion <> check:
    • Now correctly adds parentheses when inner expressions are implicitly converted multiple times. once, twice, three times a conversion ๐ŸŽถ
    • Enabled the check for C99 and later standards. C99 girlies we're so back ๐Ÿ’š
    • Now allows implicit conversions from bool to integer when used as operands of logical operators (&&, ||) in C. && and || said let them cook ๐Ÿ”ฅ
  • Improved readability-inconsistent-declaration-parameter-name <> check by not enforcing parameter name consistency between a variadic parameter pack in the primary template and specific parameters in its specializations. a pack and its specializations are different people bestie ๐Ÿ’œ
  • Improved readability-math-missing-parentheses <> check by correctly diagnosing operator precedence issues inside parenthesized expressions. parentheses inside parentheses?? inception but make it math ๐ŸŒ€
  • Improved readability-qualified-auto <> check by adding the option IgnoreAliasing, that allows not looking at underlying types of type aliases. minding your own business: an option โœจ
  • Improved readability-redundant-casting <> check by fixing false negatives when explicitly cast from function pointer. function pointers stay sneaky ๐Ÿ˜ญ
  • Improved readability-redundant-control-flow <> by fixing an issue where the check would sometimes suggest deleting not only a redundant return or continue, but also unrelated lines preceding it. DELETE THE RIGHT LINES PLEASE ๐Ÿ˜ญ๐Ÿ˜ญ this one was personal
  • Improved readability-uppercase-literal-suffix <> check to recognize literal suffixes added in C++23 and C23. C++23 and C23 girlies, we know your suffixes now ๐ŸŒˆ
  • Improved readability-use-concise-preprocessor-directives <> check to generate correct fix-its for forms without a space after the directive. no space? no problem ๐Ÿ’œ
  • Improved readability-use-std-min-max <> check by ensuring that comments between the if condition and the then block are preserved when applying the fix. comments are sacred and they stay ๐Ÿซถ

๐Ÿ’€ Removed checks

๐ŸŒธ Miscellaneous

๐Ÿ”ง Improvements to include-fixer

The improvements are...

๐Ÿ”ง Improvements to clang-include-fixer

The improvements are...

๐Ÿ”ง Improvements to modularize

The improvements are...

๐Ÿ”ง Improvements to pp-trace

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


๐Ÿ‘‘ CLANG-TIDY ๐Ÿ‘‘

๐Ÿ“œ Contents

  • Clang-Tidy
    • Using Clang-Tidy
    • Clang-Tidy Automation
      • Running Clang-Tidy in Parallel
      • Running Clang-Tidy on Diff
    • Suppressing Undesired Diagnostics
  • See also:

โœจ Clang-Tidy Checks โœจ

abseil-cleanup-ctad

Suggests switching the initialization pattern of absl::Cleanup instances from the factory function to class template argument deduction (CTAD), in C++17 and higher. CTAD era bestie, the factory is over ๐Ÿ’…

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

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

becomes โœจ glow up โœจ

absl::Cleanup c1 = [] {};

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

abseil-duration-addition

Checks for cases where addition should be performed in the absl::Time domain. When adding two values, and one is known to be an absl::Time, we can infer that the other should be interpreted as an absl::Duration of a similar scale, and make that inference explicit. read the room, read the type ๐Ÿฆ‹

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. compare in the right domain, it's giving self-respect ๐Ÿ’œ

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. rare, yes. still a bug, also yes. โš ๏ธ

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. there's a function for that bestie, stop casting โœจ

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. truncation said "surprise!" โ€” use absl::Trunc if you actually mean it ๐Ÿ’€

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 https://github.com/abseil/abseil-cpp/blob/29ff6d4860070bf8fcbd39c8805d0c32d56628a3/absl/time/time.h#L137 for more information on arithmetic with absl::Duration. integer math said "I don't do fractions" ๐Ÿซถ

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: get that decimal, bestie ๐Ÿ’…

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. floating-point context? we're fixing that โœจ

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. integer versions are right there and they're faster, use them queen ๐Ÿ‘‘

Okay so heads up bestie: this check is not gonna suggest fixes for literals that contain fractional floating point values, or for non-literals. What it will do is 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 โœจ

Catches cases where the arguments you're handing to absl::Duration factory functions are getting scaled internally, and honestly you could just be using a different factory function instead. It also sniffs out arguments with a zero value and goes "bestie, just use absl::ZeroDuration()." ๐Ÿซถ

Examples:

// Original - Internal multiplication.
int x;
absl::Duration d = absl::Seconds(60 * x);

// Suggested - Use absl::Minutes instead.
absl::Duration d = absl::Minutes(x);

// Original - Internal division.
int y;
absl::Duration d = absl::Milliseconds(y / 1000.);

// Suggested - Use absl:::Seconds instead.
absl::Duration d = absl::Seconds(y);

// Original - Zero-value argument.
absl::Duration d = absl::Hours(0);

// Suggested = Use absl::ZeroDuration instead
absl::Duration d = absl::ZeroDuration();

abseil-duration-subtraction ๐Ÿฆ‹

Checks for the cases where your subtraction should really be happening over in the absl::Duration domain. The vibe: when you're subtracting two values and the first one is known to be a conversion from absl::Duration, we can infer the second one should also be read as an absl::Duration โ€” and we make that inference explicit. no cap. ๐Ÿ’…

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: just like with other clang-tidy checks, multiple fixes can overlap (think nested expressions), so not all occurrences can be transformed in one run. This especially happens with nested subtraction expressions. Run clang-tidy multiple times and it'll find and fix those overlaps. ๐Ÿ”

abseil-duration-unnecessary-conversion ๐ŸŒธ

Finds and fixes cases where absl::Duration values get converted into numeric types and then... straight back again. the way that just cancels out. ๐Ÿ˜ญ

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 isn't aligned to the scale of conversion. In that rare case where the truncation is actually the intended result, callers should use absl::Trunc to truncate explicitly. ๐Ÿซฃ

abseil-faster-strsplit-delimiter ๐Ÿ”ฅ

Finds instances of absl::StrSplit() or absl::MaxSplits() where the delimiter is a single character string literal, and swaps it for a character. The check will offer a suggestion to change the string literal into a character. It also catches code using absl::ByAnyChar() for just a single character and transforms that into a single character too.

These changes give you the exact same result, but using characters instead of single-character string literals is more efficient and more readable. iconic. ๐Ÿ‘‘

Examples:

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

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

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

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

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

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

abseil-no-internal-dependencies ๐Ÿšซ

Warns you if your code using Abseil is depending on internal details. If something lives in a namespace that includes the word "internal", your code is not allowed to depend on it, because it's an implementation detail. You cannot friend it, include it, mention it, or refer to it in any way. Doing so violates Abseil's compatibility guidelines and may result in breakage. ๐Ÿ’€

See <https://abseil.io/about/compatibility> for more info.

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 ๐ŸŒˆ

Makes sure your code doesn't open namespace absl, because that violates Abseil's compatibility guidelines. Code should not open namespace absl since it conflicts with those guidelines and may result in breakage. fr fr.

Any code that uses:

namespace absl {
 ...
}

will be prompted with a warning. โš ๏ธ

See the full Abseil compatibility guidelines <https://abseil.io/about/compatibility> for more info.


abseil-redundant-strcat-calls ๐Ÿ’…

Suggests removing unnecessary calls to absl::StrCat when the result is being passed into another call to absl::StrCat or absl::StrAppend.

Those extra calls are constructing unnecessary temporary strings, and we simply do not tolerate that. Removing them makes the code smaller and faster. periodt. ๐ŸŽ€

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 that absl::StrAppend() should be used instead.

The extra calls cause unnecessary temporary strings to be constructed, and removing them makes the code smaller and faster. slay.

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. ๐Ÿ™ˆ

abseil-string-find-startswith ๐Ÿฆ‹

Checks whether a std::string::find() or std::string::rfind() (and the corresponding std::string_view methods) result is being compared with 0, and suggests replacing it with absl::StartsWith(). This is both a readability and a performance issue, bestie. โœจ

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

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 them with absl::StrContains().

This improves readability and reduces the likelihood of accidentally mixing find() and npos from different string-like types. we love to see it. ๐Ÿซถ

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.

N.B.: okay so bestie, in the cases where an absl::Time is being converted to an integer, alignment may occur. And if your comparison depends on that alignment, then doing the comparison in the absl::Time domain may yield a different result ๐Ÿ˜ญ. In practice this is very rare, and it still indicates a bug which should be fixed. no cap.

Examples:

// Original - Comparison in the integer domain
int x;
absl::Time t;
if (x < absl::ToUnixSeconds(t)) ...

// Suggested - Compare in the absl::Time domain instead
if (absl::FromUnixSeconds(x) < t) ...

โœจ abseil-time-subtraction โœจ

Finds and fixes absl::Time subtraction expressions so the subtraction happens in the Time domain instead of the numeric domain. it's giving glow-up fr ๐Ÿ’…

There are two cases of Time subtraction in which we 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. (iykyk ๐Ÿ˜ญ)

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 at http://github.com/llvm/llvm-project/issues/new. ๐Ÿซถ

๐Ÿ’€ 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 (function stability).

If you think the check generated a false negative, please report it at http://github.com/llvm/llvm-project/issues/new. ๐Ÿ›

๐Ÿฆ‹ 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 (lambdas)
  • Indexing into a container with the same index (containers)
  • Project specific helper-functions (uncommon API),
  • Functions with a stable return value (function stability)
  • Any cross-function reasoning (cross-function). This is by design and will not change in the future. periodt.

๐ŸŽ€ 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> sor) {
  if (sor.ok()) {
    use(*sor);
  }
}

๐Ÿ“ 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> sor1) {
  if (sor1.ok()) {
    absl::optional<int> sor2 = sor1;
    use(*sor2);
  }
}

โœจ Ensuring that the status is ok using common macros

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

void f(absl::StatusOr<int> sor) {
  ABSL_DCHECK_OK(sor);
  use(*sor);
}

๐ŸŒˆ 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> sor) {
  bool safe = false;
  if (sor.ok() && SomeOtherCondition()) {
    safe = true;
  }
  // ... more code...
  if (safe) {
    use(*sor);
  }
}

๐Ÿ”ฅ 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> sor) {
  use(*sor); // unsafe: it is unclear whether the status of `sor` is ok.
}

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

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

Use ABSL_CHECK_OK to signal that you knowingly want to crash on non-OK values.

NOTE: Even though using .value() on a non-ok() StatusOr is defined to crash, it is often unintentional. That is 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> sor) {
  if (sor.ok()) {
  } else {
    use(*sor); // unsafe: it is clear that the status of `sor` is *not* ok.
  }
}

๐Ÿ‘‘ Assuming a function result to be stable

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

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

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

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

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.

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 is 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 status is ok, then passing the StatusOr<T> to another function

The check relies on local reasoning. The check and 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> sor) {
  use(*sor); // unsafe: it is unclear whether the status of `sor` is ok.
}

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

In such cases it is 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> sor) {
  if (sor.ok()) {
    g(*sor);
  }
}

๐ŸŒธ 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 sor) {
  use(*sor); // unsafe: it is unclear whether the status of `sor` 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>> sors) {
  if (sors[0].ok()) {
    use(*sors[0]); // unsafe: it is unclear whether the status of `sors[0]` is ok.
  }
}

One needs to grab a reference to a particular object and use that instead:

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

okay so real talk, a future version could level up the understanding of more safe usage patterns that involve containers. Not today bestie, but someday. ๐Ÿฆ‹

Lambdas

The check can flag unsafe StatusOr<T> accesses inside lambdas, but it isn't smart enough to propagate information from the surrounding context through the lambda. Which means this pattern gets reported as an unsafe access:

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

To dodge the whole thing, you should grab a reference to the contained object and capture that instead:

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

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

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

abseil-upgrade-duration-conversions

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

So here's the tea: the operators *=, /=, *, and / for absl::Duration currently accept an argument of class type that is convertible to an arithmetic type. And such a call currently converts the value to an int64_t, even in a case such as std::atomic<float> that would result in a lossy conversion. ๐Ÿ’€

On top of that, 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 are gonna 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. Periodt.

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 is 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 https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.


altera-kernel-name-restriction

Finds kernel files and include directives whose filename is kernel.cl, Verilog.cl, or VHDL.cl. And yes, the check is case insensitive. ๐Ÿ”

Such kernel file names cause the offline compiler to generate intermediate design files that have the same names as certain internal files, which leads to a compilation error. Rude, honestly.

Based on the Guidelines for Naming the Kernel section in the Intel FPGA SDK for OpenCL Pro Edition: Programming Guide https://www.intel.com/content/www/us/en/programmable/documentation/mwh1391807965224.html#ewa1412973930963.


altera-single-work-item-barrier

Finds OpenCL kernel functions that call a barrier function but do not call an ID function (get_local_id, get_local_id, get_group_id, or get_local_linear_id). ๐Ÿšง

These kernels may be viable single work-item kernels, but will be forced to execute as NDRange kernels if using a newer version of the Altera Offline Compiler (>= v17.01).

If you're on an older version of the Altera Offline Compiler, these kernel functions will be treated as single work-item kernels, which could be inefficient or lead to errors if NDRange semantics were intended. So like, know which one you actually want. ๐Ÿ’…

Based on the Altera SDK for OpenCL: Best Practices Guide https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.

Examples:

// error: function calls barrier but does not call an ID function.
void __kernel barrier_no_id(__global int * foo, int size) {
  for (int i = 0; i < 100; i++) {
    foo[i] += 5;
  }
  barrier(CLK_GLOBAL_MEM_FENCE);
}

// ok: function calls barrier and an ID function.
void __kernel barrier_with_id(__global int * foo, int size) {
  for (int i = 0; i < 100; i++) {
    int tid = get_global_id(0);
    foo[tid] += 5;
  }
  barrier(CLK_GLOBAL_MEM_FENCE);
}

// ok with AOC Version 17.01: the reqd_work_group_size turns this into
// an NDRange.
__attribute__((reqd_work_group_size(2,2,2)))
void __kernel barrier_with_id(__global int * foo, int size) {
  for (int i = 0; i < 100; i++) {
    foo[tid] += 5;
  }
  barrier(CLK_GLOBAL_MEM_FENCE);
}

Options

  • AOCVersion โ€” Defines the version of the Altera Offline Compiler. Defaults to 1600 (corresponding to version 16.00).

altera-struct-pack-align

Finds structs that are inefficiently packed or aligned, and recommends packing and/or aligning of said structs as needed. ๐ŸŽ€

Structs that are not packed take up more space than they should, and accessing structs that are not well aligned is inefficient.

Fix-its are provided to fix both of these issues by inserting and/or amending relevant struct attributes. Love that for us.

Based on the Altera SDK for OpenCL: Best Practices Guide https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.

// The following struct is originally aligned to 4 bytes, and thus takes up
// 12 bytes of memory instead of 10. Packing the struct will make it use
// only 10 bytes of memory, and aligning it to 16 bytes will make it
// efficient to access.
struct example {
  char a;    // 1 byte
  double b;  // 8 bytes
  char c;    // 1 byte
};

// The following struct is arranged in such a way that packing is not needed.
// However, it is aligned to 4 bytes instead of 8, and thus needs to be
// explicitly aligned.
struct implicitly_packed_example {
  char a;  // 1 byte
  char b;  // 1 byte
  char c;  // 1 byte
  char d;  // 1 byte
  int e;   // 4 bytes
};

// The following struct is explicitly aligned and packed.
struct good_example {
  char a;    // 1 byte
  double b;  // 8 bytes
  char c;    // 1 byte
} __attribute__((packed)) __attribute__((aligned(16));

// Explicitly aligning a struct to the wrong value will result in a warning.
// The following example should be aligned to 16 bytes, not 32.
struct badly_aligned_example {
  char a;    // 1 byte
  double b;  // 8 bytes
  char c;    // 1 byte
} __attribute__((packed)) __attribute__((aligned(32)));

altera-unroll-loops

Finds inner loops that have not been unrolled, as well as fully unrolled loops with unknown loop bounds or a large number of iterations. ๐ŸŒ€

Unrolling inner loops could improve the performance of OpenCL kernels. However, if they have unknown loop bounds or a large number of iterations, they cannot be fully unrolled, and should be partially unrolled.

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 https://www.altera.com/en_US/pdfs/literature/hb/opencl-sdk/aocl_optimization_guide.pdf.

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

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

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

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

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

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

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

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

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

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

Options

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

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


android-cloexec-accept

Using accept() is not recommended, okay? it's way better to use accept4(). without this flag, an opened sensitive file descriptor would stay open across a fork+exec into a lower-privileged SELinux domain, and that's giving security oopsie ๐Ÿ˜ญ

Examples:

accept(sockfd, addr, addrlen);

// becomes

accept4(sockfd, addr, addrlen, SOCK_CLOEXEC);

android-cloexec-accept4

accept4() should include SOCK_CLOEXEC in its type argument, otherwise the file descriptor leaks. without this flag, an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain ๐Ÿซฃ

Examples:

accept4(sockfd, addr, addrlen, SOCK_NONBLOCK);

// becomes

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

android-cloexec-creat

Using creat() is not the move, bestie โ€” open() is right there. use it. โœจ

Examples:

int fd = creat(path, mode);

// becomes

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

android-cloexec-dup

dup() is not recommended, use fcntl() instead because it can set the close-on-exec flag. otherwise an opened sensitive file would stay 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

epoll_create() is not recommended, use epoll_create1() โ€” it lets you do close-on-exec, which is the whole point ๐ŸŽ€

Examples:

epoll_create(size);

// becomes

epoll_create1(EPOLL_CLOEXEC);

android-cloexec-epoll-create1

epoll_create1() needs EPOLL_CLOEXEC in its type argument so the file descriptor doesn't leak. without this flag an opened sensitive file would remain open across a fork+exec to a lower-privileged SELinux domain ๐Ÿ™ˆ

Examples:

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 ๐Ÿ‘‘

Examples:

fopen("fn", "r");

// becomes

fopen("fn", "re");

android-cloexec-inotify-init

inotify_init() is not recommended, inotify_init1() is the glow-up ๐Ÿ’–

Examples:

inotify_init();

// becomes

inotify_init1(IN_CLOEXEC);

android-cloexec-inotify-init1

inotify_init1() should include IN_CLOEXEC in its type argument to avoid 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 so this one is a big one. a super 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, which is so not it. open-like functions including open(), openat(), and open64() should include O_CLOEXEC in their flags argument, periodt ๐Ÿ’…

Examples:

open("filename", O_RDWR);
open64("filename", O_RDWR);
openat(0, "filename", O_RDWR);

// becomes

open("filename", O_RDWR | O_CLOEXEC);
open64("filename", O_RDWR | O_CLOEXEC);
openat(0, "filename", O_RDWR | O_CLOEXEC);

android-cloexec-pipe

this check catches usage of pipe(). using pipe() is not recommended โ€” pipe2() is the suggested replacement, bestie. the check also adds the O_CLOEXEC flag that marks the file descriptor to be closed in child processes. without this flag a sensitive file descriptor can be leaked to a child process, potentially into a lower-privileged SELinux domain ๐Ÿซถ

Examples:

pipe(pipefd);

Suggested replacement:

pipe2(pipefd, O_CLOEXEC);

android-cloexec-pipe2

this check makes sure pipe2() is called with the O_CLOEXEC flag. the check also adds the O_CLOEXEC flag that marks the file descriptor to be closed in child processes. without this flag a sensitive file descriptor can be leaked to a child process, potentially into a lower-privileged SELinux domain โญ

Examples:

pipe2(pipefd, O_NONBLOCK);

Suggested replacement:

pipe2(pipefd, O_NONBLOCK | O_CLOEXEC);

android-cloexec-socket

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

Examples:

socket(domain, type, SOCK_STREAM);

// becomes

socket(domain, type, SOCK_STREAM | SOCK_CLOEXEC);

android-comparison-in-temp-failure-retry

this one diagnoses comparisons that look like they got placed in the argument to the TEMP_FAILURE_RETRY macro by accident. having such a use is incorrect in the vast majority of cases, and it'll often silently defeat the whole purpose of the TEMP_FAILURE_RETRY macro. sneaky ๐Ÿ˜ญ

for context, TEMP_FAILURE_RETRY is a convenience macro (https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html) provided by both glibc and Bionic. its purpose is to repeatedly run a syscall until it either succeeds, or fails for reasons other than being interrupted.

an example of the buggy usage looks like this:

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

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

if you run into 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

this check 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 get checked:

std::accumulate, std::adjacent_difference, std::adjacent_find, std::all_of, std::any_of, std::binary_search, std::copy_backward, std::copy_if, std::copy, std::count_if, std::count, std::equal_range, std::equal, std::fill, std::find_end, std::find_first_of, std::find_if_not, std::find_if, std::find, std::for_each, std::generate, std::includes, std::iota, std::is_partitioned, std::is_permutation, std::is_sorted_until, std::is_sorted, std::lexicographical_compare, std::lower_bound, std::make_heap, std::max_element, std::merge, std::min_element, std::mismatch, std::next_permutation, std::none_of, std::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. iconic โœจ

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 conversions from integer types like int to std::string or std::wstring using boost::lexical_cast, and replaces them with calls to std::to_string and std::to_wstring ๐ŸŽ€

it does not replace conversion from floating points despite the to_string overloads, because it would change the behavior. no cap, that matters ๐Ÿ™…โ€โ™€๏ธ

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

this check makes sure argument comments match parameter names ๐Ÿ‘€

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

void f(bool foo);

...

The check tries to detect typos and suggest automated fixes for them. ๐ŸŽฏ It's giving personal-assistant energy fr fr.


โœจ Options โœจ

  • StrictMode โ€” when it's false, the check ignores leading and trailing underscores and case while comparing names. When it's true, all of that counts. Default is false.
  • IgnoreSingleArgument โ€” when true, the check just ignores the single argument entirely. Default is false.
  • CommentBoolLiterals โ€” when true, the check drops argument comments in the /*ParameterName=*/ format right before a 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);
  • CommentIntegerLiterals โ€” when true, the check adds argument comments in the /*ParameterName=*/ format right before the integer literal argument. Default is false.

Before:

void foo(int MeaningOfLife);

foo(42);

After:

void foo(int MeaningOfLife);

foo(/*MeaningOfLife=*/42);
  • CommentFloatLiterals โ€” when true, the check adds argument comments in the /*ParameterName=*/ format 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);
  • CommentStringLiterals โ€” when true, the check adds argument comments in the /*ParameterName=*/ format 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");
  • CommentCharacterLiterals โ€” when true, the check adds argument comments in the /*ParameterName=*/ format right before the character literal argument. Default is false.

Before:

void foo(char *Character);

foo('A');

After:

void foo(char *Character);

foo(/*Character=*/'A');
  • CommentUserDefinedLiterals โ€” when true, 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);
  • CommentNullPtrs โ€” when true, the check adds argument comments in the /*ParameterName=*/ format right before the nullptr literal argument. Default is false.

Before:

void foo(A* Value);

foo(nullptr);

After:

void foo(A* Value);

foo(/*Value=*/nullptr);

๐ŸŒธ bugprone-assert-side-effect ๐ŸŒธ

Finds assert() with side effect. ๐Ÿ˜ฌ

The condition of assert() is only evaluated in debug builds, so a condition with a side effect can cause different behavior in debug / release builds. That's the whole vibe check failing, bestie.

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 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. The default is empty. If a name in the list contains the sequence :: it is matched against the qualified type name (i.e. namespace::Type), otherwise it is matched against only the type name (i.e. Type).

๐Ÿ’… bugprone-assignment-in-if-condition ๐Ÿ’…

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

This check finds all assignments within if conditions, including ones that are not flagged by -Wparentheses due to an extra set of parentheses, and including assignments that call an overloaded operator=(). The identified assignments violate BARR group "Rule 8.2.c" <https://barrgroup.com/embedded-systems/books/embedded-c-coding-standard/statement-rules/if-else-statements>.

int f = 3;
if(f = 4) { // This is identified by both `Wparentheses` and this check - should it have been: `if (f == 4)` ?
  f = f + 1;
}

if((f == 5) || (f = 6)) { // the assignment here `(f = 6)` is identified by this check, but not by `-Wparentheses`. Should it have been `(f == 6)` ?
  f = f + 2;
}

๐Ÿ”ฅ bugprone-bad-signal-to-kill-thread ๐Ÿ”ฅ

Finds pthread_kill function calls when a thread is terminated by raising SIGTERM signal and the signal kills the entire process, not just the individual thread. Use any signal except SIGTERM. ๐Ÿšจ

pthread_kill(thread, SIGTERM);

This check corresponds to the CERT C Coding Standard rule POS44-C. Do not use signals to terminate threads <https://wiki.sei.cmu.edu/confluence/display/c/POS44-C.+Do+not+use+signals+to+terminate+threads>.

cert-pos44-c redirects here as an alias of this check. โœจ


๐Ÿ’€ bugprone-bitwise-pointer-cast ๐Ÿ’€

Warns about code that tries to cast between pointers by means of std::bit_cast or memcpy. ๐Ÿ•ต๏ธ

The motivation is that std::bit_cast is advertised as the safe alternative to type punning via reinterpret_cast in modern C++. However, one should not blindly replace reinterpret_cast with std::bit_cast, as follows:

int x{};
-float y = *reinterpret_cast<float*>(&x);
+float y = *std::bit_cast<float*>(&x);

The drop-in replacement behaves exactly the same as reinterpret_cast, and Undefined Behavior is still invoked. std::bit_cast is copying the bytes of the input pointer, not the pointee, into an output pointer of a different type, which may violate the strict aliasing rules. However, simply looking at the code, it looks "safe", because it uses std::bit_cast which is advertised as safe. ๐Ÿ™ƒ

The solution to safe type punning is to apply std::bit_cast on value types, not on pointer types:

int x{};
float y = std::bit_cast<float>(x);

This way, the bytes of the input object are copied into the output object, which is much safer. Do note that Undefined Behavior can still occur, if there is no value of type To corresponding to the value representation produced. Compilers may be able to optimize this copy and generate identical assembly to the original reinterpret_cast version.

Code before C++20 may backport std::bit_cast by means of memcpy, or simply call memcpy directly, which is equally problematic. This is also detected by this check:

int* x{};
float* y{};
std::memcpy(&y, &x, sizeof(x));

Alternatively, if a cast between pointers is truly wanted, reinterpret_cast should be used, to clearly convey the intent and enable warnings from compilers and linters, which should be addressed accordingly. ๐Ÿ‘‘


๐Ÿฆ‹ bugprone-bool-pointer-implicit-conversion ๐Ÿฆ‹

Checks for conditions based on implicit conversion from a bool pointer to bool.

Example:

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

๐ŸŽ€ bugprone-branch-clone ๐ŸŽ€

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

return test_value(x) ? x : x;

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

Note: This check also reports situations where branches become identical only after preprocessing. ๐ŸŒˆ

bugprone-capturing-this-in-member-variable

okay so this one catches lambdas that grab the this pointer and then stash it as a class member without handling the copy and move constructors and the assignments. ๐Ÿšฉ

Capturing this in a lambda and storing it as a class member is dangerous, bestie, because that lambda can totally outlive the object it captured. And it gets worse: when the object gets copied or moved, that captured this pointer gets implicitly propagated to the new object. Most of the time people just assume the captured this points at the new object โ€” which is not the vibe โ€” and that leads to 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:

  • 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 (https://eel.is/c++draft/expr.reinterpret.cast#7). ๐Ÿซ 

Two-step type conversions via void* are discouraged for several reasons:

  • 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

This check detects chained comparison operators that can lead to unintended behavior or logical errors. ๐Ÿ‘€

Chained comparisons are expressions that use multiple comparison operators to compare three or more values. For example, the expression a < b < c compares the values of a, b, and c. 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. ๐Ÿ˜ญ

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() (https://www.securecoding.cert.org/confluence/display/c/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. ๐Ÿ˜ตโ€๐Ÿ’ซ

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. 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 โ€” bestie this one sniffs out copy constructors and copy assignment operators that go and assign stuff to the object they just copied from, or to its direct/indirect members. Rude behavior honestly.

This check corresponds to the CERT C Coding Standard rule OOP58-CPP. Copy operations must not mutate the source object <https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP58-CPP.+Copy+operations+must+not+mutate+the+source+object>.

bugprone-crtp-constructor-accessibility โ€” catches error-prone Curiously Recurring Template Pattern usage, specifically when the CRTP can be constructed from outside itself and the derived class. ๐Ÿšฉ

So the CRTP is that lil idiom where a class derives from a template class, and it passes itself as the template argument. The vibe is: if a class is meant to be a base class in this idiom, you gotta make sure it can only be instantiated when the derived class is its template argument. No sneaky side-entrances.

Example:

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

class Derived : CRTP<Derived> {};

Below are some common mistakes that let the idiom get broken, one by one.

If the constructor of a class meant to be used in a CRTP is public, then users can just construct that class all by themselves. Not it.

Example:

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

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

CRTP<int> BadInstance;

If the constructor is protected, you're safe from accidental instantiation, BUT it can hide an error when a different class gets used as the template parameter instead of the derived one. ๐Ÿ™ˆ

Example:

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

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

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

To make sure no accidental instantiation ever happens, the best practice is to make the constructor private and declare the derived class as friend. Heads up though: as a tradeoff, this also gives the derived class access to every other private member of the CRTP. That's the deal. However, constructors can still be public or protected if they are deleted. โœจ

Example:

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

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

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

CRTP<int> AlsoCompileTimeError;

๐Ÿซถ Limitations

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


bugprone-dangling-handle โ€” detects dangling references in value handles like std::string_view. These dangling references can happen when you construct handles from temporary values, and the temporary gets destroyed right after the handle is created. It's giving "I looked away for one second." ๐Ÿ˜ญ

Examples:

string_view View = string();  // View will dangle.
string A;
View = A + "A";  // still dangle.

vector<string_view> V;
V.push_back(string());  // V[0] is dangling.
V.resize(3, string());  // V[1] and V[2] will also dangle.

string_view f() {
  // All these return values will dangle.
  return string();
  string S;
  return S;
  char Array[10]{};
  return Array;
}

span<int> g() {
  array<int, 1> V;
  return {V};
  int Array[10]{};
  return {Array};
}

โญ Options

  • HandleClasses โ€” a semicolon-separated list of class names that should be treated as handles. By default 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 (aka 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. Trust the process, basically.

๐ŸŒธ References

This check corresponds to the CERT C++ Coding Standard rule MEM57-CPP. Avoid using default operator new for over-aligned types <https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM57-CPP.+Avoid+using+default+operator+new+for+over-aligned+types>.


bugprone-derived-method-shadowing-base-method โ€” finds derived class methods that shadow a (non-virtual) base class method. ๐Ÿฅ€

For something to count as "shadowing", the methods must have the same signature โ€” 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. Periodt.

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. So like, 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 is the possibility that k will be double initialized, creating a race condition. ๐Ÿ’€


bugprone-easily-swappable-parameters โ€” finds function definitions where parameters of convertible types follow each other directly, making call sites prone to calling the function with swapped (or badly ordered) arguments. The audacity.

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

Due to the potentially elaborate refactoring and API-breaking that is 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 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 โ€” bestie, this is your do-not-touch list ๐Ÿ’…. It's the roster of parameter names that should never be treated as part of a swappable adjacent parameter sequence. The value is a ;-separated list of names. Wanna ignore unnamed parameters? Add "" to the list verbatim โ€” and listen, that's not the empty string, that's the two quote characters, potentially escaped! This option is case-sensitive, fr fr.

By default, the following parameter names, and their Uppercase-initial variants are ignored: "" (unnamed parameters), iterator, begin, end, first, last, lhs, rhs.

  • IgnoredParameterTypeSuffixes โ€” the same energy but for types โœจ. This is the list of parameter type name suffixes that should never be considered part of a swappable adjacent parameter sequence. Any parameter whose type, as written in the source code, ends with an element of this option gets ignored. The value is a ;-separated list of names, and yes, it's case-sensitive!

By default, the following, and their lowercase-initial variants are ignored: bool, It, Iterator, InputIt, ForwardIt, BidirIt, RandomIt, random_iterator, ReverseIt, reverse_iterator, reverse_const_iterator, RandomIt, random_iterator, ReverseIt, reverse_iterator, reverse_const_iterator, Const_Iterator, ConstIterator, const_reverse_iterator, ConstReverseIterator. In addition, _Bool (but not _bool) is also part of the default value.

  • SuppressParametersUsedTogether โ€” mutes 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 pack their bags ๐Ÿงณ.

Right now these are the heuristics that will suppress the warning about the parameter pair involved:

  • The parameters are used in the same expression, e.g. f(a, b) or a < b.
  • The parameters are further passed to the same function to the same parameter of that function, of the same overload. E.g. f(a, 1) and f(b, 2) to some f(T, int).
Note: the check does not perform path-sensitive analysis, and as such, "same function" in this context means the same function declaration. If the same member function of a type on two distinct instances are called with the parameters, it will still be regarded as "same function".
  • The same member field is accessed, or member method is called of the two parameters, e.g. a.foo() and b.foo().
  • Separate return statements return either of the parameters on different code paths.
  • NamePrefixSuffixSilenceDissimilarityThreshold โ€” the number of characters two parameter names might be different on either the head or the tail end with the rest of the name the same so that the warning about the two parameters gets silenced ๐Ÿคซ. Defaults to 1. Might be any positive integer. If 0, the filtering heuristic based on the parameters' names is turned off.

This option can be used to silence warnings about parameters where the naming scheme indicates that the order of those parameters do not matter.

For example, the parameters LHS and RHS are 1-dissimilar suffixes of each other: L and R is the different character, while HS is the common suffix. Similarly, parameters text1, text2, text3 are 1-dissimilar prefixes of each other, with the numbers at the end being the dissimilar part. If the value is at least 1, such cases will not be reported. slay ๐ŸŒธ

โœจ Limitations โœจ

This check is designed to check function signatures! ๐Ÿ‘‘

The check does not investigate functions that are generated by the compiler in a context that is only determined from a call site. These cases include variadic functions, functions in C code that do not have an argument list, and C++ template instantiations. Most of these cases, which are otherwise swappable from a caller's standpoint, have no way of getting "fixed" at the definition point. In the case of C++ templates, only primary template definitions and explicit specializations are matched and analyzed.

None of the following cases produce a diagnostic:

int printf(const char *Format, ...) { /* ... */ }
int someOldCFunction() { /* ... */ }

template <typename T, typename U>
int add(T X, U Y) { return X + Y };

void theseAreNotWarnedAbout() {
    printf("%d %d\n", 1, 2);   // Two ints passed, they could be swapped.
    someOldCFunction(1, 2, 3); // Similarly, multiple ints passed.

    add(1, 2); // Instantiates 'add<int, int>', but that's not a user-defined function.
}

Due to the limitation above, parameters which type are further dependent upon template instantiations to prove that they mix with another parameter's is not diagnosed.

template <typename T>
struct Vector {
  typedef T element_type;
};

// Diagnosed: Explicit instantiation was done by the user, we can prove it
// is the same type.
void instantiated(int A, Vector<int>::element_type B) { /* ... */ }

// Diagnosed: The two parameter types are exactly the same.
template <typename T>
void exact(typename Vector<T>::element_type A,
           typename Vector<T>::element_type B) { /* ... */ }

// Skipped: The two parameters are both 'T' but we cannot prove this
// without actually instantiating.
template <typename T>
void falseNegative(T A, typename Vector<T>::element_type B) { /* ... */ }

In the context of implicit conversions (when ModelImplicitConversions is enabled), the modelling performed by the check warns if the parameters are swappable and the swapped order matches implicit conversions. It does not model whether there exists an unrelated third type from which both parameters can be given in a function call. This means that in the following example, even while strs() clearly carries the possibility to be called with swapped arguments (as long as the arguments are string literals), will not be warned about. ๐Ÿ˜ญ

struct String {
    String(const char *Buf);
};

struct StringView {
    StringView(const char *Buf);
    operator const char *() const;
};

// Skipped: Directly swapping expressions of the two type cannot mix.
// (Note: StringView -> const char * -> String would be **two**
// user-defined conversions, which is disallowed by the language.)
void strs(String Str, StringView SV) { /* ... */ }

// Diagnosed: StringView implicitly converts to and from a buffer.
void cStr(StringView SV, const char *Buf() { /* ... */ }

๐Ÿ’€ bugprone-empty-catch ๐Ÿ’€

Detects and suggests addressing issues with empty catch statements. Not the vibe, bestie, not the vibe ๐Ÿ™ˆ.

try {
  // Some code that can throw an exception
} catch(const std::exception&) {
}

Having empty catch statements in a codebase can be a serious problem that developers should be aware of. Catch statements are used to handle exceptions that are thrown during program execution. When an exception is thrown, the program jumps to the nearest catch statement that matches the type of the exception.

Empty catch statements, also known as "swallowing" exceptions, catch the exception but do nothing with it. This means that the exception is not handled properly, and the program continues to run as if nothing happened. This can lead to several issues, such as:

  • Hidden Bugs: if an exception is caught and ignored, it can lead to hidden bugs that are difficult to diagnose and fix. The root cause of the problem may not be apparent, and the program may continue to behave in unexpected ways.
  • Security Issues: ignoring exceptions can lead to security issues, such as buffer overflows or null pointer dereferences. Hackers can exploit these vulnerabilities to gain access to sensitive data or execute malicious code.
  • Poor Code Quality: empty catch statements can indicate poor code quality and a lack of attention to detail. This can make the codebase difficult to maintain and update, leading to longer development cycles and increased costs.
  • Unreliable Code: code that ignores exceptions is often unreliable and can lead to unpredictable behavior. This can cause frustration for users and erode trust in the software.

To avoid these issues, developers should always handle exceptions properly. This means either fixing the underlying issue that caused the exception or propagating the exception up the call stack to a higher-level handler. If an exception is not important, it should still be logged or reported in some way so that it can be tracked and addressed later.

If the exception is something that can be handled locally, then it should be handled within the catch block. This could involve logging the exception or taking other appropriate action to ensure that the exception is not ignored.

Here is an example:

try {
  // Some code that can throw an exception
} catch (const std::exception& ex) {
  // Properly handle the exception, e.g.:
  std::cerr << "Exception caught: " << ex.what() << std::endl;
}

If the exception cannot be handled locally and needs to be propagated up the call stack, it should be re-thrown or new exception should be thrown.

Here is an example:

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

In some cases, catching the exception at this level may not be necessary, and it may be appropriate to let the exception propagate up the call stack. This can be done simply by not using try/catch block.

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

Other potential solution to avoid empty catch statements is to modify the code to avoid throwing the exception in the first place. This can be achieved by using a different API, checking for error conditions beforehand, or handling errors in a different way that does not involve exceptions. By eliminating the need for try-catch blocks, the code becomes simpler and less error-prone.

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

so basically</p>, bestie: empty catch statements are a bad practice that lead to hidden bugs, security issues, poor code quality, and unreliable code. Handle your exceptions properly and your code stays robust, secure, and maintainable. periodt. ๐Ÿ’…

โœจ Options โœจ

  • IgnoreCatchWithKeywords โ€” lets you โœจignoreโœจ specific catch statements that contain certain keywords. If a catch body contains (case-insensitive) any of the keywords in this semicolon-separated option, the catch gets ignored and no warning is raised. Default value: @TODO;@FIXME. ๐Ÿ™ˆ
  • AllowEmptyCatchForExceptions โ€” lets you ignore empty catch statements for specific exception types. By default the check raises a warning whenever it spots an empty catch, no matter what type is being caught. But sometimes โ€” like when a dev intentionally wants to ignore certain exceptions or handle them elsewhere โ€” you want to allow empty catches for particular exception types. To configure it, hand over a semicolon-separated list of exception type names; if a type from that list is caught in an empty catch, no warning. 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++. However, an exception's copy constructor should not throw โ€” otherwise you get potential issues while unwinding the stack. And if an exception is thrown during stack unwinding (like, say, from the copy constructor of an exception object), the program terminates via std::terminate. yikes. ๐Ÿ’€

          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 <https://wiki.sei.cmu.edu/confluence/display/cplusplus/ERR60-CPP.+Exception+objects+must+be+nothrow+copy+constructible>. ๐Ÿ“š


๐Ÿ”ฅ bugprone-exception-escape

Finds functions which may throw an exception directly or indirectly, but should not. The functions that should not throw exceptions are these ones, bestie:

  • 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. swap() operations are expected to be non-throwing in most cases, and it's always possible to implement them in a non-throwing way โ€” plus non-throwing swap() operations are used to create move operations. And a throwing main() function also results in unexpected termination. ๐Ÿ˜ญ

Functions declared explicitly with noexcept(false) or throw(exception) get 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. ๐Ÿ’ฏ

WARNING! This check may be expensive on large source files. ๐Ÿซ 

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 function WinMain() in the Windows API to the list of the functions which should not throw. Default value is an empty string.
  • IgnoredExceptions โ€” comma separated list containing type names which are not counted as thrown exceptions in the check. Default value is an empty string.

๐ŸŒธ bugprone-float-loop-counter

Flags for loops where the induction expression has a floating-point type. ๐Ÿšฉ

References

This check corresponds to the CERT C Coding Standard rule FLP30-C. Do not use floating-point variables as loop counters <https://www.securecoding.cert.org/confluence/display/c/FLP30-C.+Do+not+use+floating-point+variables+as+loop+counters>. ๐Ÿ“–


๐Ÿ’œ bugprone-fold-init-type

This check flags type mismatches in folds <https://en.wikipedia.org/wiki/Fold_(higher-order_function)> like std::accumulate that might result in loss of precision. std::accumulate folds an input range into an initial value using the type of the latter, with operator+ by default. And that can cause loss of precision through:

  • Truncation: the following code uses a floating point range and an int initial value, so truncation happens 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);

๐Ÿงš 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 โ€” it can't suggest a fix at this point. ๐Ÿซ 


๐ŸŽ€ 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 gets called instead โ€” which can be confusing. 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 that case. We also suppress warnings for constructors like C3โ€“C6 that are guarded with an enable_if or a concept, assuming the programmer was aware of the possible hiding. โœจ

Background

For deciding whether a constructor is guarded with enable_if, we consider the types of the constructor parameters, the default values of template type parameters, and the types of non-type template parameters with a default literal value. If any part of these types is std::enable_if or std::enable_if_t, we assume the constructor is guarded. ๐Ÿ•ต๏ธโ€โ™€๏ธ


๐ŸŒˆ bugprone-implicit-widening-of-multiplication-result

The check diagnoses instances where a result of a multiplication is implicitly widened, and suggests (with fix-it) to either silence the code by making the widening explicit, or to perform the multiplication in a wider type, so the widening is avoided afterwards. ๐Ÿ’…

This is mainly useful when you're operating on very large buffers. For example, consider:

          void zeroinit(char* base, unsigned width, unsigned height) {
            for(unsigned row = 0; row != height; ++row) {
              for(unsigned col = 0; col != width; ++col) {
                char* ptr = base + row * width + col;
                *ptr = 0;
              }
            }
          }

This is fine in general โ€” but if width * height overflows, you end up wrapping back to the beginning of base instead of processing the entire requested buffer. ๐Ÿ˜ญ

Indeed, this only matters for pretty large buffers (4GB+), but that can happen very easily, for example in image processing, where for that to happen you "only" need a ~269MPix image. ๐Ÿคฏ

Options

  • UseCXXStaticCastsInCppSources โ€” when suggesting fix-its for C++ code, should C++-style static_cast<>()'s be suggested, or C-style casts. Defaults to true.
  • UseCXXHeadersInCppSources โ€” when suggesting to include the appropriate header in C++ code, should <cstddef> header be suggested, or <stddef.h>. Defaults to true.
  • IgnoreConstantIntExpr โ€” if the multiplication operands are compile-time constants (like literals, or anything constexpr) and they actually fit inside the source expression type, then please, do not emit a diagnostic and do not suggest a fix. Only expressions where the source expression is a signed integer type get considered at all. 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 ๐Ÿ’€

bestie, this one catches you using erase() wrong. And honestly? everyone does it at least once.

So here's the tea: algorithms like remove() do not actually remove anything from the container. They just hand you back an iterator pointing at the first redundant element sitting at the end. Those leftovers have to be yeeted with erase(). This check yells at you when not all the elements will get removed, because you picked an inappropriate overload. it's giving "half a job".

For example, this code erases literally one element:

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

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

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

there. slay โœจ


bugprone-inc-dec-in-conditions ๐Ÿ™ˆ

Detects when a variable is both incremented/decremented and referenced inside one complex condition, and suggests you move that whole situation outside, because the value of the variable is otherwise giving major ambiguity energy.

When a variable gets modified and used in the same complex condition, the behavior can go places you did not intend. The side-effect of changing the variable's value inside the condition makes the code hard to reason about, periodt. Plus, when the developer meant to modify it is not clear at all, which leads to misunderstandings and errors. It's especially messy when the condition has logical operators like && and ||, because then the order of evaluation jumps in and complicates everything even more.

Consider this iconic example:

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

The result here may not be what the developer intended, ngl. Maybe they thought i gets incremented after the entire condition is evaluated โ€” but no. In reality i gets incremented before i > 0 runs. Cue the bugs. ๐Ÿ˜ญ

To fix it, separate the increment from the condition and do it on its own line. Like, increment i in a separate statement before or after the condition is evaluated. Then i's value is predictable and consistent all the way through.

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

Another classic: multiple increments or decrements on the same variable inside one complex condition. Example:

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

Potential issue here, and it's all about the order of evaluation in C++. The || operator in that condition guarantees that if the first operand evaluates to true, the second operand will not be evaluated. So if i started at 4, the first operand i++ < 5 evaluates to true and the second operand i > 2 never gets evaluated. Which means the decrement --i never executes, and i ends up holding 5 โ€” probably not the vibe the developer was going for.

To dodge this entirely, move both the increment and the decrement on i 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 it is declaring an unnamed template type parameter with a default type equal to typename std::enable_if<condition>::type. But if the author forgets to name that nested type type, then the code will always consider the candidate template โ€” even when the condition is not met. oops. ๐Ÿ’…

Here's some code using std::enable_if correctly, plus the incorrect examples 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 makes this error way less likely. 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, bestie. โœจ


bugprone-incorrect-enable-shared-from-this ๐Ÿ’œ

Detects classes or structs that do not publicly inherit from std::enable_shared_from_this, because unintended behavior will otherwise occur when you call shared_from_this. and you do not want that.

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 the libstdc++ implementation, shared_from_this will throw std::bad_weak_ptr. And when using_not_public() is called, this code will crash without exception handling. ๐Ÿ’€


bugprone-incorrect-roundings ๐Ÿ”ฅ

Checks for usage of patterns that are known to produce incorrect rounding. Programmers keep writing this:

(int)(double_expression + 0.5)

to round a double expression to an integer. And the problem with it is:

  1. It is unnecessarily slow.
  2. It is incorrect. The number 0.499999975 (the smallest representable float number below 0.5) rounds to 1.0. And it's even worse for negative numbers, where both -0.5f and -1.4f round to 0.0. ๐Ÿ’€

bugprone-infinite-loop โ™พ๏ธ

Finds the obvious infinite loops โ€” meaning loops where the condition variable is not changed at all.

Look, finding infinite loops is famously impossible (halting problem, iykyk). But some obvious ones are detectable, like when the loop condition never changes. This check catches those. A loop counts as infinite if it has no loop exit statement at all (break, continue, goto, return, throw, or a call to a function called as [[noreturn]]) and all of the following 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.

On top of that, the condition must not contain a function call for the loop to be considered infinite, since functions may return different values on different calls.

For example, this loop is considered infinite because i is not changed in the body:

int i = 0, j = 0;
while (i < 10) {
  ++j;
}

bugprone-integer-division โž—

Finds cases where integer division happening in a floating point context is likely to cause unintended loss of precision. ๐ŸŽ€

No reports are made if the divisions are part of these 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,

because all of those read as signs of deliberateness from the programmer. respect. ๐Ÿ‘‘

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++, default initialization happens if a variable is initialized with an initializer list or in other implicit ways, and no value is specified at initialization. In those cases the value 0 is used for the initialization. And this applies to enumerations too โ€” even if the enum does not have an enumerator with value 0. So a variable with that enum type can initially contain an invalid value, if the program expects it to hold only the listed enumerator values.

The check emits a warning only if an enum variable is default-initialized (as opposed to not initialized) and the enum does not have an enumerator with value of 0. The type can be a scoped or non-scoped enum. Unions are not handled by the check (so, if it contains a member of enumeration type, that's not on the check). ๐Ÿ™ˆ

Note that the enum std::errc is always ignored, because it is expected to be default initialized, despite not defining an enumerator with the value 0.

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 bestie it does not stop at C++. This check also rolls up on array or struct initialization with initialization lists over in C land. In those cases, the elements you didn't spell out in the list (the ones with enum type, anyway) get set to 0. ๐Ÿ˜ญ

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 โ€” a semicolon-separated list of regexes naming the enums this check is not allowed to come for. Default is ::std::errc. ๐Ÿซถ

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

okay so this one catches you trying to grab the name of a function from inside a lambda. And the tea is: a lambda's name is always something like operator(), which is basically never what you were going for. ๐Ÿ’€

Example, bestie:

void FancyFunction() {
  [] { printf("Called from %s\n", __func__); }();
  [] { printf("Now called from %s\n", __FUNCTION__); }();
}

Output:

Called from operator()
Now called from operator()

Likely intended output:

Called from FancyFunction
Now called from FancyFunction

โœจ Options โœจ

  • IgnoreMacros โ€” set it to true and trying to grab a function name from inside a macro won't get diagnosed. Default value is false. ๐Ÿ™ˆ

๐ŸŒˆ bugprone-macro-parentheses ๐ŸŒˆ

Finds macros that can do unexpected things because somebody forgot the parentheses. ๐Ÿ˜ฌ

Here's the thing: macros get expanded by the preprocessor as-is. So yeah, behavior can get weird โ€” operators might get evaluated in an order you did not sign up for, unary operators can turn into binary operators, the whole situation. ๐Ÿซ 

When the replacement list has an expression, the recommendation is to wrap it in parentheses. That way the macro result is fully evaluated before anything uses it. Periodt. ๐Ÿ’…

It's also recommended to wrap the macro arguments in the replacement list with parentheses, so the argument value gets calculated properly.

This check corresponds to the CERT C Coding Standard rule PRE02-C. Macro replacement lists should be parenthesized. (iykyk โ€” https://wiki.sei.cmu.edu/confluence/display/c/PRE02-C.+Macro+replacement+lists+should+be+parenthesized) โญ


๐Ÿฆ‹ bugprone-macro-repeated-side-effects ๐Ÿฆ‹

Checks for a repeated argument with side effects in macros. that's it, that's the whole check. ๐ŸŽ€


๐Ÿ’œ bugprone-misleading-setter-of-reference ๐Ÿ’œ

Finds setter-ish member functions that take a pointer parameter and then set a reference member of the same class with the value being pointed at. sneaky.

So here's exactly what it 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 reference type.

Why does this matter? Because the fact that a setter takes a pointer might make you believe that an internal reference (if it were a pointer) is what's being changed โ€” instead of the pointed-to (or referenced) value. ๐Ÿ™ˆ

Example:

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

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

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

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

Possible fixes:

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

๐ŸŽ€ bugprone-misplaced-operator-in-strlen-in-alloc ๐ŸŽ€

Finds cases where 1 gets added to the string in the argument of strlen(), strnlen(), strnlen_s(), wcslen(), wcsnlen(), and wcsnlen_s() โ€” instead of being added to the result โ€” and then that value gets used as an argument to a memory allocation function (malloc(), calloc(), realloc(), alloca()) or to the new[] operator in C++. not the vibe.

The check still catches the error cases even if one of these functions (except the new[] operator) is called through a constant function pointer. And cases where 1 is added to both the parameter and the result of the strlen()-like function are ignored, same as cases where the whole addition is wrapped in extra parentheses. ๐Ÿซถ

C example code:

void bad_malloc(char *str) {
  char *c = (char*) malloc(strlen(str + 1));
}

The suggested fix is to add 1 to the return value of strlen() and not to its argument. In the example above the fix would be

char *c = (char*) malloc(strlen(str) + 1);

C++ example code:

void bad_new(char *str) {
  char *c = new char[strlen(str + 1)];
}

Same deal as the C code with malloc(): the suggested fix is to add 1 to the return value of strlen() and not to its argument. In the example above the fix would be

char *c = new char[strlen(str) + 1];

Example for silencing the diagnostic:

void bad_malloc(char *str) {
  char *c = (char*) malloc(strlen((str + 1)));
}

๐ŸŒŸ bugprone-misplaced-pointer-arithmetic-in-alloc ๐ŸŒŸ

Finds cases where an integer expression gets added to or subtracted from the result of a memory allocation function (malloc(), calloc(), realloc(), alloca()) instead of its argument. And yep โ€” the check still detects error cases even if one of these functions is called through 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 warns when there's a cast of a calculation result to a bigger type. Because bestie, if the whole point of that cast was to avoid losing precision, then the cast is in the wrong spot and you can still lose precision. And if that wasn't the point, then the cast isn't doing anything at all. ๐Ÿ˜ญ

Example code:

long f(int x) {
    return (long)(x * 1000);
}

The result x * 1000 gets calculated with int precision first. If that result goes past int precision, precision is lost. Then the result gets cast to long. order of operations said no.

If there's no loss of precision then the cast can just be removed, or you can explicitly cast to int instead.

If you do want to avoid loss of precision, put the cast somewhere proper, like:

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 detects those cases too, for instance:

long f(int x) {
    return x * 1000;
}

Floating point

Currently warnings are only written for integer conversion. No warning is written for this code:

double f(float x) {
    return (double)(x * 10.0f);
}

โœจ Options โœจ

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

๐Ÿ‘‘ bugprone-move-forwarding-reference ๐Ÿ‘‘

Warns if std::move gets called on a forwarding reference, like this:

template <typename T>
void foo(T&& t) {
  bar(std::move(t));
}

Forwarding references (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4164.pdf) should typically be passed to std::forward instead of std::move, and that's the fix that will be suggested. ๐Ÿ’…

(Quick definition so we're on the same page: a forwarding reference is an rvalue reference of a type that is a deduced function template argument. โœจ)

In this example, the suggested fix would be

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

Background

Code like the example above is sometimes written with the expectation that T&& will always end up being an rvalue reference, no matter what type gets deduced for T โ€” and that therefore it's not possible to pass an lvalue to foo(). But that is not true, bestie. 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().

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

Which means the foo(s) call passes s as an lvalue reference, and foo() ends up moving s and thereby placing it into an indeterminate state. ๐Ÿ™ˆ reference collapsing ate and left no crumbs.


๐Ÿ’– bugprone-multi-level-implicit-pointer-conversion ๐Ÿ’–

Detects implicit conversions between pointers of different levels of indirection. iconic in the worst way.

Conversions between pointer types of different levels of indirection can be dangerous and may lead to undefined behavior, particularly if the converted pointer is later cast to a type with a different level of indirection. For example, converting a pointer to a pointer to an int (int**) to a void* can result in the loss of information about the original level of indirection, which can cause problems when attempting to use the converted pointer. If the converted pointer is later cast to a type with a different level of indirection and dereferenced, it may lead to access violations, memory corruption, or other undefined behavior. ๐Ÿ’€

Consider the following example:

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

okay so in this lil scenario, foo() gets called with ptr_ptr as its argument. but bestie... ptr_ptr is an int** pointer, and foo() is out here expecting a void* pointer. ๐Ÿ’€ so what happens is an implicit pointer level conversion, and that can genuinely cause problems if foo() dereferences the pointer assuming it's a int* pointer. like, it's giving wrong-assumption energy.

so the recommended glow-up here is an explicit cast โ€” it stops the implicit pointer level conversion from doing sneaky stuff, because it makes the developer actually state their intention and show their reasoning for the type conversion. โœจ and also?? it is recommended that developers thoroughly check and verify the safety of the conversion before using an explicit cast. that extra layer of caution helps catch potential issues early on in the development process, which improves the overall reliability and maintainability of the code. periodt. ๐Ÿ’…


โœจ Options โœจ

  • EnableInC โ€” if true, it turns the check on in C code (it is always enabled in C++ code, no takebacks). default is true. ๐Ÿซถ

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

catches when you have multiple new operator calls chillin' in a single expression, where the memory allocated by the first new may leak if the second allocation fails and throws an exception. ๐Ÿ˜ญ

so here's the tea: C++ does often not specify the exact order of evaluation of the operands of an operator or arguments of a function. so if a first allocation succeeds and a second fails, then in an exception handler it is not possible to tell which allocation has failed and free the memory. and 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. bottom line: it is best to avoid any expression that contains more than one operator new call, if exception handling is used to check for allocation errors. ๐Ÿšซ

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

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

Examples:

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

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

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

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

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

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

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

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

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

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

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

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

bugprone-multiple-statement-macro ๐ŸŽ€

this one sniffs out 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... not the vibe you wanted.

Example:

#define INCREMENT_TWO(x, y) (x)++; (y)++
if (do_increment)
  INCREMENT_TWO(a, b);  // (b)++ will be executed unconditionally.

bugprone-narrowing-conversions ๐Ÿ’–

heads up: cppcoreguidelines-narrowing-conversions redirects here as an alias for this check. iykyk. ๐Ÿ”ฅ

it checks for silent narrowing conversions, e.g: int i = 0; i += 0.1;. and yeah the issue is obvious in that former example, but it might not be so obvious in this one: void MyClass::f(double d) { int_member_ += d; }. ๐Ÿ™ˆ

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 <https://en.cppreference.com/w/cpp/language/implicit_conversion>. this check diagnoses more instances of narrowing than the compiler warning -Wconversion does. the example below demonstrates this behavior. โœจ

// The following function definition demonstrates usage of arithmetic with
// integer types smaller than `int` and how the narrowing conversion happens
// implicitly.
void computation(short argument1, short argument2) {
  // Arithmetic written by humans:
  short result = argument1 + argument2;
  // Arithmetic actually performed by C++:
  short result = static_cast<short>(static_cast<int>(argument1) + static_cast<int>(argument2));
}

void recommended_resolution(short argument1, short argument2) {
  short result = argument1 + argument2;
  //           ^ warning: narrowing conversion from 'int' to signed type 'short' is implementation-defined

  // The cppcoreguidelines recommend to resolve this issue by using the GSL
  // in one of two ways. Either by a cast that throws if a loss of precision
  // would occur.
  short result = gsl::narrow<short>(argument1 + argument2);
  // Or it can be resolved without checking the result risking invalid results.
  short result = gsl::narrow_cast<short>(argument1 + argument2);

  // A classical `static_cast` will silence the warning as well if the GSL
  // is not available.
  short result = static_cast<short>(argument1 + argument2);
}

โœจ Options โœจ

  • WarnOnIntegerNarrowingConversion โ€” when true, the check will warn on narrowing integer conversion (e.g. int to size_t). true by default. ๐Ÿ’…
  • WarnOnIntegerToFloatingPointNarrowingConversion โ€” when true, the check will warn on narrowing integer to floating-point conversion (e.g. size_t to double). true by default. โœจ
  • WarnOnFloatingPointNarrowingConversion โ€” when true, the check will warn on narrowing floating point conversion (e.g. double to float). true by default. ๐ŸŒธ
  • WarnWithinTemplateInstantiation โ€” when true, the check will warn on narrowing conversions within template instantiations. false by default. ๐Ÿฆ‹
  • WarnOnEquivalentBitWidth โ€” when true, the check will warn on narrowing conversions that arise from casting between types of equivalent bit width. (e.g. int n = uint(0); or long long n = double(0);) true by default. ๐Ÿ‘‘
  • IgnoreConversionFromTypes โ€” narrowing conversions from any type in this semicolon-separated list will be ignored. this may be useful to weed out commonly occurring, but less commonly problematic assignments such as int n = std::vector<char>().size(); or int n = std::difference(it1, it2);. the default list is empty, but one suggested list for a legacy codebase would be size_t;ptrdiff_t;size_type;difference_type. ๐Ÿซถ
  • PedanticMode โ€” when true, the check will warn on assigning a floating point constant to an integer value even if the floating point value is exactly representable in the destination type (e.g. int i = 1.0;). false by default. ๐Ÿ™ˆ

FAQ ๐Ÿ’œ

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

an IEEE754 Floating Point number can represent all integer values in the range [-2^PrecisionBits, 2^PrecisionBits] where PrecisionBits is the number of bits in the mantissa. ๐Ÿงš

for float this would be [-2^23, 2^23], where int can represent values in the range [-2^31, 2^31-1]. and that's the whole thing bestie, the ranges just don't line up. ๐Ÿ˜ญ

  • What does "implementation-defined" mean? โ€” okay bestie, so you might've bumped into messages like "narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined". Here's the tea: the C/C++ standard does not mandate two's complement for signed integers, so the compiler gets to decide for itself what the semantics are when you convert an unsigned integer to a signed integer. Clang's implementation? It uses the two's complement format. That's it, that's the whole vibe. ๐Ÿ’…

๐Ÿฆ‹ bugprone-no-escape

Finds pointers with the noescape attribute that are captured by an asynchronously-executed block. The block arguments in dispatch_async() and dispatch_after() are guaranteed to escape, so it is an error if a pointer with the noescape attribute is captured by one of these blocks. ๐Ÿ˜ญ

The following is an example of an invalid use of the noescape attribute.

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

โœจ bugprone-non-zero-enum-to-bool-conversion

Detects implicit and explicit casts of enum type into bool where the enum type doesn't have a zero-value enumerator. If the enum is used only to hold values equal to its enumerators, then conversion to bool will always result in a true value. That can lead to unnecessary code that reduces readability and maintainability and can result in bugs. Not the slay we wanted. ๐Ÿ™ˆ

It may produce false positives if the enum is used to store other values (used as a bit-mask or zero-initialized on purpose). To deal with them, // NOLINT or casting first to the underlying type before casting to bool can be used.

It is important to note that this check will not generate warnings if the definition of the enumeration type is not available. Additionally, C++11 enumeration classes are supported by this check.

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

Example

enum EStatus {
  OK = 1,
  NOT_OK,
  UNKNOWN
};

void process(EStatus status) {
  if (!status) {
    // this true-branch won't be executed
    return;
  }
  // proceed with "valid data"
}

Options

  • EnumIgnoreList โ€” this option is used to ignore certain enum types when checking for implicit/explicit casts to bool. It accepts a semicolon-separated list of (fully qualified) enum type names or regular expressions that match the enum type names. The default value is an empty string, which means no enums will be ignored.

๐Ÿงš bugprone-nondeterministic-pointer-iteration-order

Finds nondeterministic usages of pointers in unordered containers. ๐Ÿซถ

One canonical example is iteration across a container of pointers.

{
  int a = 1, b = 2;
  std::unordered_set<int *> UnorderedPtrSet = {&a, &b};
  for (auto i : UnorderedPtrSet)
    f(i);
}

Another such example is sorting a container of pointers.

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

Iteration of a containers of pointers may present the order of different pointers differently across different runs of a program. In some cases this may be acceptable behavior, in others this may be unexpected behavior. This check is advisory for this reason. ๐ŸŒธ

This check only detects range-based for loops over unordered sets and maps. It also detects calls sorting-like algorithms on containers holding pointers. Other similar usages will not be found and are false negatives.

Limitations

  • This check currently does not check if a nondeterministic iteration order is likely to be a mistake, and instead marks all such iterations as bugprone.
  • std::reference_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 is possible to cause a not null-terminated result. Usually the proper length of a string is strlen(src) + 1 or equal length of this expression, because the null terminator needs an extra space. Without the null terminator it can result in undefined behavior when the string is read. ๐Ÿ’€

The following and their respective wchar_t based functions are checked:

memcpy, memcpy_s, memchr, memmove, memmove_s, strerror_s, strncmp, strxfrm

The following is a real-world example where the programmer forgot to increase the passed third argument, which is size_t length. That is why the length of the allocated memory is not enough to hold the null terminator.

static char *stringCpy(const std::string &str) {
  char *result = reinterpret_cast<char *>(malloc(str.size()));
  memcpy(result, str.data(), str.size());
  return result;
}

In addition to issuing warnings, fix-it rewrites all the necessary code. It also tries to adjust the capacity of the destination array:

static char *stringCpy(const std::string &str) {
  char *result = reinterpret_cast<char *>(malloc(str.size() + 1));
  strcpy(result, str.data());
  return result;
}
Note: it cannot guarantee to rewrite every of the path-sensitive memory allocations. ๐Ÿ˜ญ

Transformation rules of memcpy()

It is possible to rewrite the memcpy() and memcpy_s() calls as the following four functions: strcpy(), strncpy(), strcpy_s(), strncpy_s(), where the latter two are the safer versions of the former two. It rewrites the wchar_t based memory handler functions respectively.

Rewrite based on the destination array

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

[1] It is possible to overflow:

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

Rewrite based on the length of the source string

  • If the given length is strlen(source) or equal length of this expression then the new function should be the older copy function (ending with cpy), as it is more efficient than the safe version (ending with cpy_s).
  • Otherwise we assume that the programmer wanted to copy 'N' characters, so the new function is ncpy-like which copies 'N' characters.

Transformations with strlen() or equal length of this expression

It transforms the wchar_t based memory and string handler functions respectively (where only strerror_s does not have wchar_t based alias).

Memory handler functions

  • memcpy โ€” please visit the Transformation rules of memcpy() section.
  • memchr โ€” usually there is a C-style cast and it is needed to be removed, because the new function strchr's return type is 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 which is the length of the destination array, it is adjusted, and the length of the source string is incremented by one. If safe functions are not available the given length is incremented by one.
  • memmove_s โ€” the given length is incremented by one.

String handler functions

  • strerror_s โ€” the given length is incremented by one.
  • strncmp โ€” if the third argument is the first or the second argument's length + 1 it has to be truncated without the + 1 operation.
  • strxfrm โ€” the given length is incremented by one.

Options

  • WantToUseSafeFunctions โ€” the value true specifies that the target environment is considered to implement '_s' suffixed memory and string handler functions which are safer than older versions (e.g. 'memcpy_s()'). The default value is true.

๐ŸŒˆ bugprone-optional-value-conversion

Detects potentially unintentional and redundant conversions where a value is extracted from an optional-like type and then used to create a new instance of the same optional-like type. ๐ŸŽ€

These conversions might be the result of developer oversight, leftovers from code refactoring, or other situations that could lead to unintended exceptions or cases where the resulting optional is always initialized, which might be unexpected behavior.

To illustrate, consider the following problematic code snippet:

#include <optional>

void print(std::optional<int>);

int main()
{
  std::optional<int> opt;
  // ...

  // Unintentional conversion from std::optional<int> to int and back to
  // std::optional<int>:
  print(opt.value());

  // ...
}

A better approach would be to directly pass opt to the print function without extracting its value:

#include <optional>

void print(std::optional<int>);

int main()
{
  std::optional<int> opt;
  // ...

  // Proposed code: Directly pass the std::optional<int> to the print
  // function.
  print(opt);

  // ...
}

By passing opt directly to the print function, unnecessary conversions are avoided, and potential unintended behavior or exceptions are minimized. Obsessed with the glow-up 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 overridden parent's virtual methods. ๐Ÿ‘‘

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

okay bestie this one catches you doing pointer math on classes that have a virtual function in them. which, no cap, is a whole vibe violation.

Here's the tea: when you do pointer arithmetic on a polymorphic object and the pointer's static type is different from its dynamic type, that's undefined behavior. Why? because the two types could literally be different sizes, so the vtable pointer could end up pointing at a totally invalid address. it's giving chaos.

The heuristic it uses is: if the static type has a virtual member function, the pointer is probably pointing at some derived object, so it flags it. pretty solid logic ngl.

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 it's true, objects that only inherit a virtual function don't get checked. Classes that don't declare a new virtual function are excluded by default, because they make up the majority of false positives. Default: false.
void bar(Base b[], Derived d[]) {
  b += 1; // warning, as Base declares a virtual destructor
  d += 1; // warning only if IgnoreVirtualDeclarationsOnly is set to false
}

References

This check corresponds to the SEI Cert rule CTR56-CPP. Do not use pointer arithmetic on polymorphic objects <https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR56-CPP.+Do+not+use+pointer+arithmetic+on+polymorphic+objects>.


bugprone-posix-return ๐Ÿซถ

Checks if any calls to pthread_* or posix_* functions (except posix_openpt, she's exempt) are expecting negative return values. bestie. these functions return either 0 on success or an errno on failure โ€” and an errno is positive only. so checking for < 0 is literally never gonna hit. ๐Ÿ’€

Example of the buggy usage looking 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 they're initialized or seeded with a default argument, a constant expression, or any user-configurable type. Because a PRNG seeded with a predictable value can cause vulnerabilities โ€” like, actual security protocol problems. it's giving "why is my crypto broken" ๐Ÿ˜ญ

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 <https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC51-CPP.+Ensure+your+random+number+generator+is+properly+seeded> and MSC32-C. Properly seed pseudorandom number generators <https://wiki.sei.cmu.edu/confluence/display/c/MSC32-C.+Properly+seed+pseudorandom+number+generators>.


bugprone-raw-memory-call-on-non-trivial-type ๐Ÿšจ

Flags use of the C standard library functions memset, memcpy and memcmp โ€” plus similar derivatives โ€” on non-trivial types. like girl. don't raw-memory a whole object with a destructor. pls.

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 <https://wiki.sei.cmu.edu/confluence/display/cplusplus/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 in between. it's giving "girl we already know" ๐Ÿ’…

Simple example:

bool onFire = isBurning();
if (onFire) {
  if (onFire)
    scream();
}

Here onFire is checked both in the outer if and the inner if statement without a possible change between the two checks. The check warns for this code and suggests removal of the second checking of variable onFire.

The checker also detects redundant condition checks if the condition variable is an operand of a logical "and" (&&) or a logical "or" (||) operator:

bool onFire = isBurning();
if (onFire) {
  if (onFire && peopleInTheBuilding > 0)
    scream();
}

bool onFire = isBurning();
if (onFire) {
  if (onFire || isCollapsing())
    scream();
}

In the first case (logical "and") the suggested fix is to remove the redundant condition variable and keep the other side of the &&. In the second case (logical "or") the whole if is removed similarly to the simple case on the top.

The condition of the outer if statement may also be a logical "and" (&&) expression:

bool onFire = isBurning();
if (onFire && fireFighters < 10) {
  if (someOtherCondition()) {
    if (onFire)
      scream();
  }
}

The error is also detected if both the outer statement is a logical "and" (&&) and the inner statement is a logical "and" (&&) or "or" (||). The inner if statement does not have to be a direct descendant of the outer one.

No error is detected if the condition variable may have been changed between the two checks:

bool onFire = isBurning();
if (onFire) {
  tryToExtinguish(onFire);
  if (onFire && peopleInTheBuilding > 0)
    scream();
}

Every possible change is considered, thus if the condition variable is not a local variable of the function, it is a volatile or it has an alias (pointer or reference) then no warning is issued.

Limitations

The else branch is not checked currently for negated condition variable:

bool onFire = isBurning();
if (onFire) {
  scream();
} else {
  if (!onFire) {
    continueWork();
  }
}

The checker currently only detects redundant checking of single condition variables. More complex expressions are not checked:

if (peopleInTheBuilding == 1) {
  if (peopleInTheBuilding == 1) {
    doSomething();
  }
}

bugprone-reserved-identifier ๐Ÿ‘‘

cert-dcl37-c and cert-dcl51-cpp redirect here as an alias for this check.

Checks for usages of identifiers reserved for use by the implementation. The C and C++ standards both reserve the following names for such use:

  • identifiers that begin with an underscore followed by an uppercase letter;
  • identifiers in the global namespace that begin with an underscore.

The C standard additionally reserves names beginning with a double underscore, while the C++ standard strengthens this to reserve names with a double underscore occurring anywhere.

Violating the naming rules above results in undefined behavior. ๐Ÿซ 
namespace NS {
  void __f(); // name is not allowed in user code
  using _Int = int; // same with this
  #define cool__macro // also this
}
int _g(); // disallowed in global namespace only

The check can also be inverted, i.e. it can be configured to flag any identifier that is not a reserved identifier. This mode is for use by e.g. standard library implementors, to ensure they don't infringe on the user namespace.

This check does not (yet) check for other reserved names, e.g. macro names identical to language keywords, and names specifically reserved by language standards, e.g. C++ 'zombie names' and C future library directions.

This check corresponds to CERT C Coding Standard rule DCL37-C. Do not declare or define a reserved identifier <https://wiki.sei.cmu.edu/confluence/display/c/DCL37-C.+Do+not+declare+or+define+a+reserved+identifier> as well as its C++ counterpart, DCL51-CPP. Do not declare or define a reserved identifier <https://wiki.sei.cmu.edu/confluence/display/cplusplus/DCL51-CPP.+Do+not+declare+or+define+a+reserved+identifier>.

Options

  • Invert โ€” 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 a constant reference. This may cause use-after-free errors if the caller uses xvalues as arguments. bestie this is the bad kind of slay ๐Ÿ’€

In C++, constant reference parameters can accept xvalues which will be destructed after the call. When the function returns such a parameter also as constant reference, then the returned reference can be used after the object it refers to has been destroyed.

Example

struct S {
  int v;
  S(int);
  ~S();
};

const S &fn(const S &a) {
  return a;
}

const S& s = fn(S{1});
s.v; // use after free

This issue can be resolved by declaring an overload of the problematic function where the const & parameter is instead declared as &&. The developer has to ensure that the implementation of that function does not produce a use-after-free โ€” which is, like, literally the exact error this check is warning against ๐Ÿ˜ญ. Marking such an && overload as deleted will silence the warning too, no cap. And if different const & parameters get returned depending on the control flow of the function, then an overload where all the problematic const & parameters have been declared as && will resolve the issue. ๐Ÿ’…

This issue can also be resolved by adding [[clang::lifetimebound]]. Clang enables -Wdangling by default, which can detect mis-uses of the annotated function. See lifetimebound attribute https://clang.llvm.org/docs/AttributeReference.html#lifetimebound for details. ๐Ÿฆ‹

const int &f(const int &a [[clang::lifetimebound]]) { return a; } // no warning
const int &v = f(1); // warning: temporary bound to local reference 'v' will be destroyed at the end of the full-expression [-Wdangling]

bugprone-shared-ptr-array-mismatch

okay bestie this one is SO good โ€” it finds initializations of C++ shared pointers to a non-array type that are initialized with an array. ๐Ÿ’€

So here's the tea: if a shared pointer std::shared_ptr<T> gets initialized with a new-expression new T[], the memory does not get deallocated correctly. The pointer uses plain delete in this case to deallocate the target memory. Instead a delete[] call is needed. A std::shared_ptr<T[]> calls the correct delete operator. โœจ

The 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 https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM51-CPP.+Properly+deallocate+dynamically+allocated+resources โ€” however, only the std::shared_ptr case is detected by this check. (So like, don't go thinking it's catching everything, bestie ๐Ÿซถ)


bugprone-signal-handler

Finds specific constructs in signal handler functions that can cause undefined behavior ๐Ÿ˜ฑ. The rules for what's allowed differ between C++ language versions.

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. Like, it just dips out.

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 be 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 https://www.securecoding.cert.org/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers and the rule MSC54-CPP. A signal handler must be a plain old function https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC54-CPP.+A+signal+handler+must+be+a+plain+old+function. It has the alias names cert-sig30-c and cert-msc54-cpp. ๐Ÿ‘‘


Options

  • AsyncSafeFunctionSet โ€” selects which set of functions is considered as asynchronous-safe (and therefore allowed in signal handlers). It can be set to the following values:
_Exit, _exit, abort, accept, access, aio_error, aio_return, aio_suspend, alarm, bind,
cfgetispeed, cfgetospeed, cfsetispeed, cfsetospeed, chdir, chmod, chown, clock_gettime,
close, connect, creat, dup, dup2, execl, execle, execv, execve, faccessat, fchdir,
fchmod, fchmodat, fchown, fchownat, fcntl, fdatasync, fexecve, ffs, fork, fstat,
fstatat, fsync, ftruncate, futimens, getegid, geteuid, getgid, getgroups, getpeername,
getpgrp, getpid, getppid, getsockname, getsockopt, getuid, htonl, htons, kill, link,
linkat, listen, longjmp, lseek, lstat, memccpy, memchr, memcmp, memcpy, memmove,
memset, mkdir, mkdirat, mkfifo, mkfifoat, mknod, mknodat, ntohl, ntohs, open, openat,
pause, pipe, poll, posix_trace_event, pselect, pthread_kill, pthread_self,
pthread_sigmask, quick_exit, raise, read, readlink, readlinkat, recv, recvfrom,
recvmsg, rename, renameat, rmdir, select, sem_post, send, sendmsg, sendto, setgid,
setpgid, setsid, setsockopt, setuid, shutdown, sigaction, sigaddset, sigdelset,
sigemptyset, sigfillset, sigismember, siglongjmp, signal, sigpause, sigpending,
sigprocmask, sigqueue, sigset, sigsuspend, sleep, sockatmark, socket, socketpair,
stat, stpcpy, stpncpy, strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat,
strncmp, strncpy, strnlen, strpbrk, strrchr, strspn, strstr, strtok_r, symlink,
symlinkat, tcdrain, tcflow, tcflush, tcgetattr, tcgetpgrp, tcsendbreak, tcsetattr,
tcsetpgrp, time, timer_getoverrun, timer_gettime, timer_settime, times, umask, uname,
unlink, unlinkat, utime, utimensat, utimes, wait, waitpid, wcpcpy, wcpncpy, wcscat,
wcschr, wcscmp, wcscpy, wcscspn, wcslen, wcsncat, wcsncmp, wcsncpy, wcsnlen, wcspbrk,
wcsrchr, wcsspn, wcsstr, wcstok, wmemchr, wmemcmp, wmemcpy, wmemmove, wmemset, write

The function quick_exit is not included in the POSIX list but it is included here in the set of safe functions. ๐Ÿ™ˆ

The default value is POSIX.


bugprone-signed-char-misuse

cert-str34-c redirects here as an alias for this check. For the CERT alias, the DiagnoseSignedUnsignedCharComparisons option is set to false.

Finds those signed char -> integer conversions which might indicate a programming error. The basic problem with 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. ๐Ÿ˜ตโ€๐Ÿ’ซ

When the code contains an explicit signed char -> integer conversion, the human programmer probably expects that the converted value matches with the character code (a value from [0..255]) โ€” however, the actual value is in the [-128..127] interval. To avoid this kind of misinterpretation, the desired way of converting from a signed char to an integer value is converting to unsigned char first, which stores all the characters in the positive [0..255] interval which matches the known character codes. โœจ

In case of implicit conversion, the programmer might not actually be aware that a conversion happened and the char value is used as an integer. There are some use cases when this unawareness might lead to functionally imperfect code. For example, checking the equality of a signed char and an unsigned char variable is something we should avoid in C++ code. During this comparison, the two variables are converted to integers which have different value ranges. For signed char, the non-ASCII characters are stored as a value in [-128..-1] interval, while the same characters are stored in the [128..255] interval for an unsigned char.

It depends on the actual platform whether plain char is handled as signed char by default and so 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 https://wiki.sei.cmu.edu/confluence/display/c/STR34-C.+Cast+characters+to+unsigned+char+before+converting+to+larger+integer+sizes

A good example from the CERT description 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;
}

okay and there's another whole use case, bestie, and it's the sneaky one ๐Ÿซถ It's about checking whether two char variables are equal when they have different signedness. Inside the non-ASCII value range, that comparison between a signed char and an unsigned char always returns false. Always. Every time. Not a vibe. ๐Ÿ’€

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. One lil static_cast and we're so back โœจ

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. like girl. that's not what sizeof does ๐Ÿ’…

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. it's giving "you thought you ate but you didn't" ๐Ÿ˜ญ

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. Misuse of this operator may be leading to errors and possible software vulnerabilities. so yeah, highkey worth checking.

โœจ 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. bestie that's a pointer, not the whole fit ๐Ÿ’€

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 /. not the multiplication arc i fear ๐Ÿ™ˆ

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

Conversely, when performing pointer arithmetics to add or subtract from a pointer, the arithmetic operator implicitly scales the value actually added to the pointer with the size of the pointee, as Ptr + N expects N to be "number of objects to step", and not "number of bytes to step".

Seeing the calculation of a pointer where sizeof appears is suspicious, and the result is typically unintended, often out of bounds. Ptr + sizeof(T) will offset the pointer by sizeof(T) elements, effectively exponentiating the scaling factor to the power of 2.

Similarly, multiplying or dividing a numeric value with the sizeof of an element or the whole buffer is suspicious, because the dimensional connection between the numeric value and the actual sizeof result can not always be deduced. While scaling an integer up (multiplying) with sizeof is likely always an issue, a scaling down (division) is not always inherently dangerous, in case the developer is aware that the division happens between an appropriate number of bytes and a sizeof value. Turning WarnOnOffsetDividedBySizeOf off will restrict the warnings to the multiplication case.

This case also checks suspicious alignof and offsetof usages in pointer arithmetic, as both return the "size" in bytes and not elements, potentially resulting in doubly-scaled offsets.

void printEveryEvenIndexElement(int *Array, size_t N) {
  int *P = Array;
  while (P <= Array + N * sizeof(int)) { // Suspicious pointer arithmetic using sizeof()!
    printf("%d ", *P);

    P += 2 * sizeof(int); // Suspicious pointer arithmetic using sizeof()!
  }
}

struct Message { /* ... */; char Flags[8]; };
void clearFlags(Message *Array, size_t N) {
  const Message *End = Array + N;
  while (Array < End) {
    memset(Array + offsetof(Message, Flags), // Suspicious pointer arithmetic using offsetof()!
           0, sizeof(Message::Flags));
    ++Array;
  }
}

For this checked bogus pattern, cert-arr39-c redirects here as an alias of this check.

This check corresponds to the CERT C Coding Standard rule ARR39-C. Do not add or subtract a scaled integer to a pointer http://wiki.sei.cmu.edu/confluence/display/c/ARR39-C.+Do+not+add+or+subtract+a+scaled+integer+to+a+pointer.

๐ŸŒˆ Limitations

Cases where the pointee type has a size of 1 byte (such as, and most importantly, char) are excluded.

๐ŸŒˆ Options

  • WarnOnSizeOfConstant โ€” when true, the check will warn on an expression like sizeof(CONSTANT). Default is true.
  • WarnOnSizeOfIntegerExpression โ€” turn this on and the check yells at you for stuff like sizeof(expr) when expr is an integer. Default is false.
  • WarnOnSizeOfThis โ€” when true, it warns on expressions like sizeof(this). Default is true.
  • WarnOnSizeOfCompareToConstant โ€” when true, it warns on expressions like sizeof(expr) <= k for a suspicious constant k, where k is 0 or bigger than 0x8000. Default is true.
  • WarnOnSizeOfPointerToAggregate โ€” when true, the check warns 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 (which could be implicit or explicit, like array + 2 or (int *)array). Default is true.
  • WarnOnSizeOfPointer โ€” when true, the check reports every expression where the argument of sizeof is something that produces a pointer (except for a few idiomatic expressions that are probably intentional and correct). This catches occurrences of CWE 467. Default is false.
  • WarnOnOffsetDividedBySizeOf โ€” when true, the check warns on pointer arithmetic where the element count comes from a division with sizeof(...), e.g. Ptr + Bytes / sizeof(*T). Default is true.
  • WarnOnSizeOfInLoopTermination โ€” when true, the check warns about incorrect use of a sizeof expression in a loop termination condition. It triggers when the sizeof expression looks like it's being wrongly used to work out 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

Bestie, this one catches 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 when the function has no condition parameter. ๐Ÿ’€

if (condition_predicate) {
    condition.wait(lk);
}

if (condition_predicate) {
    if (thrd_success != cnd_wait(&condition, &lock)) {
    }
}

This check corresponds to the CERT C++ Coding Standard rule CON54-CPP. Wrap functions that can spuriously wake up in a loop https://wiki.sei.cmu.edu/confluence/display/cplusplus/CON54-CPP.+Wrap+functions+that+can+spuriously+wake+up+in+a+loop. and CERT C Coding Standard rule CON36-C. Wrap functions that can spuriously wake up in a loop https://wiki.sei.cmu.edu/confluence/display/c/CON36-C.+Wrap+functions+that+can+spuriously+wake+up+in+a+loop.


๐ŸŒธ bugprone-standalone-empty

Warns when empty() is used on a range and the result justโ€ฆ gets ignored. ๐Ÿ™ˆ It suggests clear() if that's an existing member function.

The empty() method on several common ranges returns a Boolean saying whether the range is empty or not, but people keep misreading it as a way to clear the contents of a range. Some ranges offer a clear() method for exactly that. This check warns when a call to empty returns a result that's ignored, and suggests swapping in a call to clear() if clear() is available as a member function of the range.

For example, this 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 ignored result in the class template definition (for example in the library implementation). These error cases can be caught with the [[nodiscard]] attribute.

๐Ÿ”ฅ bugprone-std-namespace-modification

Warns on modifications of the std or posix namespaces, which can result in undefined behavior. ๐Ÿšจ

The std (or posix) namespace is allowed to be extended with (class or function) template specializations that depend on a user-defined type (a type that is not defined in the standard system headers).

The 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 https://www.securecoding.cert.org/confluence/display/cplusplus/DCL58-CPP.+Do+not+modify+the+standard+namespaces.


๐ŸŽ€ bugprone-string-constructor

Finds string constructors that are suspicious and probably errors. ๐Ÿ‘€

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.

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.

Examples:

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

Passing an invalid first character position parameter to the constructor will cause a std::out_of_range exception at runtime.

Examples:

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

โญ Options โญ

  • WarnOnLargeLength โ€” when true, the check warns 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. 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

This check finds assignments of an integer to std::basic_string<CharT> (std::string, std::wstring, etc.). The source of the problem is this assignment operator of std::basic_string<CharT>:

basic_string& operator=( CharT ch );

Numeric types can be implicitly casted to character types. ๐Ÿ˜ญ

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

Use the appropriate conversion functions or character literals. โœจ

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 a string literal with an 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 because they keep track of the bytes and the length. That is not the case for a char* (NUL-terminated) string.

A common mistake is to pass a string-literal with an 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 the various ways the const CharT* constructor of std::basic_string_view can be passed a null argument, and replaces them with the default constructor in most cases. For the comparison operators, a braced initializer list does not compile, so instead a call to .empty() or the empty string literal is 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 the trailing comment "A" picks the (const CharT*) constructor overload and then value-initializes the pointer, which means null dereference city ๐Ÿ˜ญ. It happens to not include the nullptr literal, but bestie, it's still inside the scope of this ClangTidy check. No escaping it.
Note: the source pattern with the trailing comment "B" picks the (const CharT*, size_type) constructor, and that one is perfectly valid โœจ because the length argument is 0. This ClangTidy check does not touch it. Leave her alone, she's thriving.

bugprone-suspicious-enum-usage

The checker catches all the little moments when an enum is probably being misused โ€” specifically, used like a bitmask when it absolutely is not one. ๐Ÿ’…

  1. When "ADD" or "bitwise OR" is used between two enums that come from different types, and those types' value ranges are not disjoint.

The following cases get investigated only when StrictMode is on. We regard an enum as a (suspicious) bitmask if all 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 that non pow-of-2 element gets used as a bitmask element, we diagnose a misuse and hand you 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 gets investigated in addition to the different enum usage check. So basically: crank it up and she looks at everything. ๐Ÿ‘‘

bugprone-suspicious-include

The check sniffs out all the cases where an include points at what really looks like an implementation file, which is famously how you end up with ODR violations that are impossible to track down. ๐Ÿ’€

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. These are the cases it's covering:

Case 1: Non-standard-layout type

Comparing the object representations of non-standard-layout objects may not properly compare the value representations.

Case 2: Types with no unique object representation

Objects with the same value may not have the same object representation. This may be caused by padding or floating-point types. ๐Ÿ™ˆ

See also: EXP42-C. Do not compare padding data https://wiki.sei.cmu.edu/confluence/display/c/EXP42-C.+Do+not+compare+padding+data and FLP37-C. Do not use object representations to compare floating-point values https://wiki.sei.cmu.edu/confluence/display/c/FLP37-C.+Do+not+use+object+representations+to+compare+floating-point+values

This check is also related to โ€” and partially overlaps โ€” the CERT C++ Coding Standard rules OOP57-CPP. Prefer special member functions and overloaded operators to C Standard Library functions https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP57-CPP.+Prefer+special+member+functions+and+overloaded+operators+to+C+Standard+Library+functions and EXP62-CPP. Do not access the bits of an object representation that are not part of the object's value representation https://wiki.sei.cmu.edu/confluence/display/cplusplus/EXP62-CPP.+Do+not+access+the+bits+of+an+object+representation+that+are+not+part+of+the+object%27s+value+representation

cert-exp42-c redirects here as an alias of this check. โœจ


bugprone-suspicious-memset-usage

This check finds memset() calls with potential mistakes in their arguments. Think of the function as void* memset(void* destination, int fill_value, size_t byte_count), and then these are the cases it covers:

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 get concatenated at translation phase 6 (after the preprocessor). This feature is used to represent a long string literal on multiple lines. ๐ŸŽ€

For instance, the following declarations are equivalent:

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

A common mistake done by programmers is forgetting a comma between two string literals in an array initializer list. ๐Ÿ˜ญ

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

The array contains the string "line 2line3" at offset 1 (i.e. Test[1]). Clang won't generate warnings at compile time. Not me finding out the hard way ๐Ÿ™ˆ

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

This check finds usages of realloc where the return value is assigned to the same expression as passed to the first argument: p = realloc(p, size);

The problem with this construct is that if realloc fails it returns a null pointer but does not deallocate the original memory. If no other variable is pointing to it, the original memory block is not available any more for the program to use or free. In either case p = realloc(p, size); indicates bad coding style and can be replaced by q = realloc(p, size);. ๐Ÿ’…

The pointer expression (used at realloc) can be a variable or a field member of a data structure, but can not contain function calls or unresolved types.

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

Examples:

struct A {
  void *p;
};

A &getA();

void foo(void *p, A *a, int new_size) {
  p = realloc(p, new_size); // warning: 'p' may be set to null if 'realloc' fails, which may result in a leak of the original buffer
  a->p = realloc(a->p, new_size); // warning: 'a->p' may be set to null if 'realloc' fails, which may result in a leak of the original buffer
  getA().p = realloc(getA().p, new_size); // no warning
}

void foo1(void *p, int new_size) {
  void *p1 = p;
  p = realloc(p, new_size); // no warning
}

bugprone-suspicious-semicolon

Finds most instances of stray semicolons that unexpectedly alter the meaning of the code. More specifically, it looks for if, while, for and for-range statements whose body is a single semicolon, and then analyzes the context of the code (e.g. indentation) in an attempt to determine whether that is intentional. ๐Ÿ”

if (x < y);
{
  x++;
}

Here the body of the if statement consists of only the semicolon at the end of the first line, and x will be incremented regardless of the condition. The audacity.

while ((line = readLine(file)) != NULL);
  processLine(line);

As a result of this code, processLine() will only be called once, when the while loop with the empty body exits with line == NULL. The indentation of the code indicates the intention of the programmer. ๐Ÿ˜ญ

if (x >= y);
x -= y;

While the indentation does not imply any nesting, there is simply no valid reason to have an if statement with an empty body (but it can make sense for a loop). So this check issues a warning for the code above. โญ

Either yeet the stray semicolon, or โ€” if that empty body is on purpose โ€” make it obvious with indentation, or shove the semicolon onto its own line. Like:

while (readWhitespace());
  Token t = readNextToken();

Here that second line is indented in a way that screams "I'm the body of the while loop" โ€” but bestie, the body is literally empty, because of the semicolon chilling at the end of line one. ๐Ÿซ 

So either un-indent the second line:

while (readWhitespace());
Token t = readNextToken();

...or move the semicolon off the end of line one and onto a fresh line:

while (readWhitespace())
  ;

  Token t = readNextToken();

When you do that? The check assumes you know exactly what you're doing and just... doesn't warn. Iconic of it, honestly. โœจ


bugprone-suspicious-string-compare ๐Ÿ“

Catches sus usage of runtime string comparison functions. Valid in both C and C++, no cap.

It looks for calls with an implicit comparator and gently suggests you add the comparator out loud:

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) get compared against a valid constant. The resulting value is:

<  0    when lower than,
>  0    when greater than,
== 0    when equals.

A classic oopsie 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 lands in the wrong context:

if (strcmp(...) < 0.)  // Incorrect usage of the returned value.

Options

  • WarnOnImplicitComparison โ€” when true, the check will warn on implicit comparison. true by default.
  • WarnOnLogicalNotComparison โ€” when true, the check will warn on logical not comparison. false by default.
  • StringCompareLikeFunctions โ€” a string specifying the comma-separated names of the extra string comparison functions. Default is an empty string. The check will detect the following string comparison functions:
__builtin_memcmp, __builtin_strcasecmp, __builtin_strcmp,
__builtin_strncasecmp, __builtin_strncmp, _mbscmp, _mbscmp_l,
_mbsicmp, _mbsicmp_l, _mbsnbcmp, _mbsnbcmp_l, _mbsnbicmp,
_mbsnbicmp_l, _mbsncmp, _mbsncmp_l, _mbsnicmp, _mbsnicmp_l,
_memicmp, _memicmp_l, _stricmp, _stricmp_l, _strnicmp,
_strnicmp_l, _wcsicmp, _wcsicmp_l, _wcsnicmp, _wcsnicmp_l,
lstrcmp, lstrcmpi, memcmp, memicmp, strcasecmp, strcmp, strcmpi,
stricmp, strncasecmp, strncmp, strnicmp, wcscasecmp, wcscmp,
wcsicmp, wcsncmp, wcsnicmp, wmemcmp.

bugprone-suspicious-stringview-data-usage ๐Ÿฆ‹

Spots sus usages of std::string_view::data() that could lead to reading out-of-bounds data, because the string's null termination is inadequate or just plain wrong. ๐Ÿ˜ญ

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 devs 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 result in unintended behavior if the API relies on proper null termination for correct string interpretation.

In today's programming landscape, this scenario can pop up 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 will result in a compilation error. As a workaround, devs may be tempted to reach for the .data() method just to get it to 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, due to the explicit nature of the std::string constructor. Consequently, devs 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 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 โ€” a warning is raised.

void printNumbers(int a, float b);

int main() {
  // Swapped arguments: float passed as int, int as float)
  printNumbers(10.0f, 5);
  return 0;
}

Covers a wide range of implicit conversions, including:

  • User-defined conversions
  • Conversions from floating-point types to boolean or integral types
  • Conversions from integral types to boolean or floating-point types
  • Conversions from boolean to integer types or floating-point types
  • Conversions from (member) pointers to boolean

Important note bestie: 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 the developer intentionally used different integral or floating-point types, and no warning is raised. This approach prevents false positives and provides flexibility in handling situations where varying integral or floating-point types are intentionally utilized. ๐Ÿ’…


bugprone-switch-missing-default-case ๐ŸŒธ

Makes sure switch statements without default cases get flagged โ€” and it focuses only on 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 statement will do nothing and the program will just continue executing without handling the value. ๐Ÿ˜ณ

This check helps identify switch statements that are missing a default case, letting devs make sure all possible cases are handled properly. Adding a default case allows for graceful handling of unexpected or unmatched values, reducing the risk of program errors and unexpected behavior.

Example:

// Example 1:
// warning: switching on non-enum value without default case may not cover all cases
switch (i) {
case 0:
  break;
}

// Example 2:
enum E { eE1 };
E e = eE1;
switch (e) { // no-warning
case eE1:
  break;
}

// Example 3:
int i = 0;
switch (i) { // no-warning
case 0:
  break;
default:
  break;
}
Note: Enum types are already covered by compiler warnings (comes under -Wswitch) when 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 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#res-default> 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. Vibe check failed, basically. ๐Ÿ˜ญ

A struct or a class is considered to be a tagged union if it has exactly one union data member and exactly one enum data member, plus any number of other data members that are neither unions nor enums. 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
};

okay so the tea here is that this check is counting distinct values, not the enum constants themselves. So if two constants are basically the same value wearing a different outfit โ€” aliases, bestie โ€” they only count once. Iconic.

The counting-enum-constant situation (stuff like TagCount up there ๐Ÿ‘†) gets handled with a lil heuristic: if the last</> enum constant's name starts with a prefix or ends with a suffix that's in CountingEnumPrefixes / CountingEnumSuffixes, and its value is exactly one less than the total number of distinct values in the enum, then the count goes down by one. girl math, but make it compilers ๐Ÿ’…

When the final count gets adjusted by this heuristic, you get a diagnostic note telling you which enum constant matched. Cute, right?

You can turn the whole heuristic off (EnableCountingEnumHeuristic) or make it match your own naming convention (CountingEnumPrefixes, CountingEnumSuffixes). The strings you put in CountingEnumPrefixes and CountingEnumSuffixes are matched case insensitively, no cap.

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 switches the counting-enum heuristic on or off. It uses the prefixes and suffixes from CountingEnumPrefixes and CountingEnumSuffixes to sniff out counting enum constants, doing prefix and suffix matching on them. <sparkle>โœจ</sparkle>

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

CountingEnumSuffixes

CountingEnumPrefixes and CountingEnumSuffixes are lists of semicolon separated strings that get used to hunt 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 absolutely 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 this is enabled, the check also warns you when the number of tags is greater than the number of union data members. extra strict, extra slay

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

Catches do while loops where the condition always evaluates to false and there's a continue in there โ€” because that continue basically just ends the loop. like girl, that's not a continue, that's a goodbye ๐Ÿ˜ญ

void f() {
do {
  // some code
  continue; // terminating continue
  // some other code
} while(false);

๐ŸŒธ bugprone-throw-keyword-missing ๐ŸŒธ

Warns about a throw keyword that's maybe missing. If a temporary object gets created, but that object's type derives from (or <em>is</em>) a class with 'EXCEPTION', 'Exception' or 'exception' somewhere in its name, we're allowed to assume the programmer's whole intention was to throw that object. <sparkle>โœจ</sparkle>

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. main() hasn't even started yet, bestie, chill

๐Ÿ“š References ๐Ÿ“š

This check corresponds to the CERT C++ Coding Standard rule ERR58-CPP. Handle all exceptions thrown before main() begins executing <https://www.securecoding.cert.org/confluence/display/cplusplus/ERR58-CPP.+Handle+all+exceptions+thrown+before+main%28%29+begins+executing>.


๐ŸŽ€ bugprone-too-small-loop-variable ๐ŸŽ€

Detects those for loops where the loop variable has a "too small" type โ€” meaning the type can't represent all the values that are part of the iteration range. vibe check: failed

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 the values in the [0..size] interval. forever loop, no exit, main character energy but the bad kind ๐Ÿ’€

In a real use case, size means a container's size which depends on user input.

int doSomething(const std::vector& items) {
  for (short i = 0; i < items.size(); ++i) {}
}

This algorithm works fine for a small amount of objects, but it will freeze for larger user input.

It's recommended to also enable the compiler warning -Wtautological-constant-out-of-range-compare, since the check does not inspect compile-time constant loop boundaries โ€” that's to avoid overlapping with the warning. teamwork makes the dream work ๐Ÿซถ

โœจ Options โœจ

  • MagnitudeBitsUpperLimit โ€” upper limit for the magnitude bits of the loop variable. If it's set, the check filters out those catches where the loop variable's type has more magnitude bits than the specified upper limit. The default value is 16. For example, if the user sets this option to 31 (bits), then a 32-bit unsigned int is ignored by the check, however a 32-bit int is not (a 32-bit signed int has 31 magnitude bits).
int main() {
  long size = 294967296l;
  for (unsigned i = 0; i < size; ++i) {} // no warning with MagnitudeBitsUpperLimit = 31 on a system where unsigned is 32-bit
  for (int i = 0; i < size; ++i) {} // warning with MagnitudeBitsUpperLimit = 31 on a system where int is 32-bit
}

๐ŸŒˆ bugprone-unchecked-optional-access ๐ŸŒˆ

Note: This check uses a flow-sensitive static analysis to produce its results. Therefore, it may be more resource intensive (RAM, CPU) than the average clang-tidy check. she's thorough, she's expensive, she's worth it

This check identifies unsafe accesses to values contained in std::optional<T>, absl::optional<T>, base::Optional<T>, folly::Optional<T>, bsl::optional, or BloombergLP::bdlb::NullableValue objects. Below we'll refer to all these types collectively as optional<T>. <sparkle>โœจ</sparkle>

An access to the value of an optional<T> happens when one of its value, operator*, or operator-> member functions gets invoked. To align with common misconceptions, the check treats these member functions as equivalent โ€” even though there are subtle differences related to exceptions versus undefined behavior. See Additional notes, below, for more on that topic.

An access to the value of an optional<T> is considered 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 the access. That should happen either through an explicit check using the optional<T>::has_value member function, or by constructing the optional<T> in a way that shows it unambiguously holds a value (e.g. using std::make_optional, which always returns a populated std::optional<T>). periodt ๐Ÿ’…

Below we list some examples, starting with unsafe optional access patterns, followed by safe access patterns.

๐Ÿ’€ Unsafe access patterns ๐Ÿ’€

Access the value without checking if it exists

The check flags accesses to the value that 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 aware of the state of an optional object in different branches of the code. For example:

void f(std::optional<int> opt) {
  if (opt.has_value()) {
  } else {
    use(opt.value()); // unsafe: it is clear that `opt` does *not* have a value.
  }
}

Assume a function result to be stable

okay so bestie, plot twist ๐ŸŒˆ โ€” the check is fully aware that function results might not be stable. Meaning: consecutive calls to the same function might hand you back different values. It's giving chaos. For example:

void f(Foo foo) {
  if (foo.take().has_value()) {
    use(*foo.take()); // unsafe: it is unclear whether `foo.take()` has a value.
  }
}

Exception: accessor methods

The check assumes accessor methods of a class are stable, using a lil heuristic to determine which methods are accessors โœจ. Specifically, parameter-free const methods and smart pointer-like APIs (non const overloads of * when there is a parallel const overload) are treated as accessors. Note that this is not guaranteed to be safe โ€” but, it is widely used (safely) in practice ๐Ÿ’…. Calls to non const methods are assumed to modify the state of the object and affect the stability of earlier accessor calls.


Rely on invariants of uncommon APIs

The check is unaware of invariants of uncommon APIs ๐Ÿ™ˆ. For example:

void f(Foo foo) {
  if (foo.HasProperty("bar")) {
    use(*foo.GetProperty("bar")); // unsafe: it is unclear whether `foo.GetProperty("bar")` has a value.
  }
}

Check if a value exists, then pass the optional to another function

The check relies on local reasoning only, fr fr. The check and the value access must both happen in the same function. An access is considered unsafe even if the caller of the function performing the access ensures that the optional has a value ๐Ÿ˜ญ. For example:

void g(std::optional<int> opt) {
  use(*opt); // unsafe: it is unclear whether `opt` has a value.
}

void f(std::optional<int> opt) {
  if (opt.has_value()) {
    g(opt);
  }
}

โœจ Safe access patterns โœจ

now THIS is the section where we thrive ๐Ÿซถ these are the patterns that keep you out of trouble.

Check if a value exists, then access the value

The check recognizes all the straightforward ways for checking if a value exists and then accessing the value contained in an optional object ๐Ÿ‘‘. For example:

void f(std::optional<int> opt) {
  if (opt.has_value()) {
    use(*opt);
  }
}

Check if a value exists, then access the value from a copy

The criteria that the check uses is semantic, not syntactic ๐Ÿงš. It recognizes when a copy of the optional object being accessed is known to have a value. For example:

void f(std::optional<int> opt1) {
  if (opt1.has_value()) {
    std::optional<int> opt2 = opt1;
    use(*opt2);
  }
}

Ensure that a value exists using common macros

The check is aware of common macros like CHECK and DCHECK ๐ŸŒธ. Those can be used to ensure that an optional object has a value. For example:

void f(std::optional<int> opt) {
  DCHECK(opt.has_value());
  use(*opt);
}

Ensure that a value exists, then access the value in a correlated branch

The check is aware of correlated branches in the code and can figure out when an optional object is ensured to have a value on all execution paths that lead to an access ๐Ÿ”ฅ. For example:

void f(std::optional<int> opt) {
  bool safe = false;
  if (opt.has_value() && SomeOtherCondition()) {
    safe = true;
  }
  // ... more code...
  if (safe) {
    use(*opt);
  }
}

Stabilize function results

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 is best to store the result of the function call in a local variable and then use that variable to access the value ๐Ÿ’–. For example:

void f(Foo foo) {
  if (const auto& foo_opt = foo.take(); foo_opt.has_value()) {
    use(*foo_opt);
  }
}

Do not rely on uncommon-API invariants

When uncommon APIs guarantee that an optional has contents, do not rely on it โ€” instead, check explicitly that the optional object has a value. For example:

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

instead of the HasProperty, GetProperty pairing we saw above.

Do not rely on caller-performed checks

If you know that all of a function's callers have checked that an optional argument has a value, either change the function to take the value directly, or check the optional again in the local scope of the callee. For example:

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

void f(std::optional<int> opt) {
  if (opt.has_value()) {
    g(*opt);
  }
}

and

struct S {
  std::optional<int> opt;
  int x;
};

void g(const S &s) {
  if (s.opt.has_value() && s.x > 10) {
    use(*s.opt);
}

void f(S s) {
  if (s.opt.has_value()) {
    g(s);
  }
}

๐ŸŒˆ Additional notes ๐ŸŒˆ

Aliases created via using declarations

The check is aware of aliases of optional types that are created via using declarations โญ. For example:

using OptionalInt = std::optional<int>;

void f(OptionalInt opt) {
  use(opt.value()); // unsafe: it is unclear whether `opt` has a value.
}

Lambdas

The check does not currently report unsafe optional accesses in lambdas ๐Ÿ™ˆ. A future version will expand the scope to lambdas, following the rules outlined above. It is best to follow the same principles when using optionals in lambdas.

Access with operator*() vs. value()

Given that value() has well-defined behavior (either throwing an exception or terminating the program), why treat it the same as operator*() which causes undefined behavior (UB)? That is, why is it considered unsafe to access an optional with value(), if it's not provably populated with a value? For that matter, why is CHECK() followed by operator*() any better than value(), given that they are semantically equivalent (on configurations that disable exceptions)?

The answer is that we assume most users do not realize the difference between value() and operator*(). Shifting to operator*() and some form of explicit value-presence check or explicit program termination has two advantages:

  • Readability. The check, and any potential side effects like program shutdown, are very clear in the code. Separating access from checks can actually make the checks more obvious. ๐Ÿ’…
  • Performance. A single check can cover many or even all accesses within scope. This gives the user the best of both worlds โ€” the safety of a dynamic check, but without incurring redundant costs. ๐Ÿ”ฅ

๐ŸŽ€ 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 avoid false positives where the analysis cannot equate results across such calls. Heads up though: this does not cover access through operator[]. Default is false.
  • IgnoreValueCalls โ€” if set to true, the check does not diagnose calls to optional::value(). Diagnostics for operator*() and operator->() remain enabled. This is useful for codebases that intentionally rely on value() for defined, guarded access while still flagging UB-prone operator dereferences. Default is false.

๐Ÿ’€ bugprone-unchecked-string-to-number-conversion

This check flags calls to string-to-number conversion functions that do not verify the validity of the conversion, such as atoi() or scanf() ๐Ÿ‘€. It does not flag calls to strtol(), or other, related conversion functions that do perform better error checking.

#include <stdlib.h>

void func(const char *buff) {
  int si;

  if (buff) {
    si = atoi(buff); /* 'atoi' used to convert a string to an integer, but function will
                         not report conversion errors; consider using 'strtol' instead. */
  } else {
    /* Handle error */
  }
}

References

This check corresponds to the CERT C Coding Standard rule ERR34-C. Detect errors when converting a string to a number <https://www.securecoding.cert.org/confluence/display/c/ERR34-C.+Detect+errors+when+converting+a+string+to+a+number>.


๐Ÿซ  bugprone-undefined-memory-manipulation

Finds calls of memory manipulation functions memset(), memcpy() and memmove() on non-TriviallyCopyable objects resulting in undefined behavior. yikes.

Using memory manipulation functions on non-TriviallyCopyable objects can lead to a range of subtle and challenging issues in C++ code ๐Ÿ˜ญ. The most immediate concern is the potential for undefined behavior, where the state of the object may become corrupted or invalid. This can manifest as crashes, data corruption, or unexpected behavior at runtime, making it challenging to identify and diagnose the root cause. Additionally, misuse of memory manipulation functions can bypass essential object-specific operations, such as constructors and destructors, leading to resource leaks or improper initialization.

For example, when using memcpy to copy std::string, pointer data is being copied, and it can result in a double free issue ๐Ÿ’€.

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


bugprone-unhandled-exception-at-new

Finds calls to new with missing exception handler for std::bad_alloc ๐Ÿ˜ฌ.

Calls to new may throw exceptions of type std::bad_alloc that should be handled. Alternatively, the nonthrowing form of new can be used. The check verifies that the exception is handled in the function that calls new.

If a nonthrowing version is used or the exception is allowed to propagate out of the function no warning is generated.

The exception handler is checked if it catches a std::bad_alloc or std::exception exception type, or all exceptions (catch-all). The check assumes that any user-defined operator new is either noexcept or may throw an exception of type std::bad_alloc (or one derived from it). Other exception class types are not taken into account.

int *f() noexcept {
  int *p = new int[1000]; // warning: missing exception handler for allocation failure at 'new'
  // ...
  return p;
}
          int *f1() { // not 'noexcept'
            int *p = new int[1000]; // no warning: exception can be handled outside
                                    // of this function
            // ...
            return p;
          }

          int *f2() noexcept {
            try {
              int *p = new int[1000]; // no warning: exception is handled
              // ...
              return p;
            } catch (std::bad_alloc &) {
              // ...
            }
            // ...
          }

          int *f3() noexcept {
            int *p = new (std::nothrow) int[1000]; // no warning: "nothrow" is used
            // ...
            return p;
          }

bugprone-unhandled-self-assignment

cert-oop54-cpp redirects here as an alias for this check, okay bestie? ๐Ÿ’– And heads up โ€” for the CERT alias specifically, the WarnOnlyIfThisHasSuspiciousField option is set to false.

So what does it do? It finds user-defined copy assignment operators that 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. Like, pick a lane, please. ๐Ÿซถ

By default, this check only searches those classes that have any pointer or C array field โ€” because it's trying to avoid false positives. And why? Because in the case of a pointer or a C array, it's likely that self-copy assignment breaks the object if the copy assignment operator wasn't written with care. ๐Ÿ’€

See also: OOP54-CPP. Gracefully handle self-copy assignment <https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP54-CPP.+Gracefully+handle+self-copy+assignment>

Okay so the whole vibe: 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. Not me deleting the thing I'm about to read fromโ€ฆ ๐Ÿ˜ญ

          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. Pattern number one 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;
            }
          };

And then there's 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 (which 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 โ€” and this is the plot twist ๐Ÿ˜ญ โ€” 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.

          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. Slay, it read your mind โœจ:

          std::cout << static_cast<unsigned int>(v); // when v is unsigned char
          std::cout << static_cast<int>(v); // when v is signed char

To avoid lengthy cast statements, adding prefix + to the variable can also suppress warnings, because the unary expression will promote the value to an int. Lowkey iconic. ๐ŸŽ€

          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

Finds initializations of C++ unique pointers to non-array type that are initialized with an array. ๐Ÿšจ

If a pointer std::unique_ptr<T> is initialized with a new-expression new T[], the memory is not deallocated correctly. A plain delete is used in this case to deallocate the target memory โ€” but instead a delete[] call is needed. A std::unique_ptr<T[]> uses the correct delete operator. The check does not emit a warning if a unique_ptr with a user-specified deleter type is used. ๐Ÿ’…

The check offers replacement of unique_ptr<T> to unique_ptr<T[]> if it is used at a single variable declaration (one variable in one statement).

Example:

          std::unique_ptr<Foo> x(new Foo[10]); // -> std::unique_ptr<Foo[]> x(new Foo[10]);
          //                     ^ warning: unique pointer to non-array is initialized with array
          std::unique_ptr<Foo> x1(new Foo), x2(new Foo[10]); // no replacement
          //                                   ^ warning: unique pointer to non-array is initialized with array

          D d;
          std::unique_ptr<Foo, D> x3(new Foo[10], d); // no warning (custom deleter used)

          struct S {
            std::unique_ptr<Foo> x(new Foo[10]); // no replacement in this case
            //                     ^ warning: unique pointer to non-array is initialized with array
          };

This check partially covers the CERT C++ Coding Standard rule MEM51-CPP. Properly deallocate dynamically allocated resources <https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM51-CPP.+Properly+deallocate+dynamically+allocated+resources> โ€” however, only the std::unique_ptr case is detected by this check. Just so you know. ๐Ÿ™ˆ


bugprone-unsafe-functions

Checks for functions that have safer, more secure replacements available, or are considered deprecated due to design flaws. The check heavily relies on the functions from the Annex K. "Bounds-checking interfaces" of C11. ๐Ÿ”ฅ

The check implements the following rules from the CERT C Coding Standard:

cert-msc24-c and cert-msc33-c redirect here as aliases of this check.

Unsafe functions

The following functions are reported if ReportDefaultFunctions is enabled. ๐Ÿ‘€

If Annex K. is available, a replacement from Annex K. is suggested for the following functions:

asctime, asctime_r, bsearch, ctime, fopen, fprintf, freopen, fscanf, fwprintf, fwscanf, getenv, gets, gmtime, localtime, mbsrtowcs, mbstowcs, memcpy, memmove, memset, printf, qsort, scanf, snprintf, sprintf, sscanf, strcat, strcpy, strerror, strlen, strncat, strncpy, strtok, swprintf, swscanf, vfprintf, vfscanf, vfwprintf, vfwscanf, vprintf, vscanf, vsnprintf, vsprintf, vsscanf, vswprintf, vswscanf, vwprintf, vwscanf, wcrtomb, wcscat, wcscpy, wcslen, wcsncat, wcsncpy, wcsrtombs, wcstok, wcstombs, wctomb, wmemcpy, wmemmove, wprintf, wscanf.

If Annex K. is not available, replacements are suggested only for the following functions from the previous list:

  • asctime, asctime_r, suggested replacement: strftime
  • gets, suggested replacement: fgets

The following functions are always checked, regardless of Annex K availability:

  • rewind, suggested replacement: fseek
  • setbuf, suggested replacement: setvbuf

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 cap. ๐Ÿซก

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 reason is optional, and it's there to spill the extra tea about why the replacement is the move. If you don't give one, the default reason gets marked as unsafe.

If replacement is empty, the default text it should not be used shows up instead of any replacement suggestion. Empty means empty, bestie.

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 โ€” that's so you get better control over the suggestions. (The starting > and the whitespace directly after it are trimmed from the message.)

So like, for example, this config matches only the function original in the default namespace. A similar diagnostic can also be printed using a fully custom reason. Look:

// 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's matched against the qualified name (i.e. std::original). Otherwise the regex is matched against the unqualified name (original). And if the regular expression starts with :: (or ^::), it's matched against the fully qualified name (::std::original).

One of the use cases for fully custom messages is suggesting compiler options and warning flags, iconic honestly:

// 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. sneaky little caveat, but okay

โœจ Options โœจ

  • ReportMoreUnsafeFunctions โ€” when true, extra functions from widely used APIs (like POSIX) get added to the list of reported functions. Peep the main documentation of the check for the complete list of what this option enables. Default is true.
  • ReportDefaultFunctions โ€” when true, the check reports the default set of functions. Consider flipping it to false if you only wanna 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-unused-local-non-trivial-variable

Warns when a local non trivial variable is unused within a function. Basically it's a vibe check on your locals. The following types of variables are excluded from this check:

  • trivial and trivially copyable
  • references and pointers
  • exception variables in catch clauses
  • static or thread local
  • structured bindings
  • variables with [[maybe_unused]] attribute
  • name-independent variables

This check can be configured to warn on all non-trivial variables by setting IncludeTypes to .*, and excluding specific types using ExcludeTypes.

In this example, my_lock would generate a warning that it is unused.

std::mutex my_lock;
// my_lock local variable is never used

In the next example, future2 would generate a warning that it is unused.

std::future<MyObject> future1;
std::future<MyObject> future2;
// ...
MyObject foo = future1.get();
// future2 is not used.

โœจ Options โœจ

  • IncludeTypes โ€” semicolon-separated list of regular expressions matching types of variables to check. By default the following types are checked:
    • ::std::.*mutex
    • ::std::future
    • ::std::basic_string
    • ::std::basic_regex
    • ::std::basic_istringstream
    • ::std::basic_stringstream
    • ::std::bitset
    • ::std::filesystem::path
  • ExcludeTypes โ€” a semicolon-separated list of regular expressions matching types that are excluded from the IncludeTypes matches. By default it is an empty list.

bugprone-unused-raii

Finds temporaries that look like RAII objects. the fake-out is real

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

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. yes you can customize the vibes

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 โ€” this one's the boss of whether you're allowed to cast a return value to void. Default: false.

cert-err33-c <> is an alias of this check, and bestie it checks a fixed and large set of standard library functions. Like, a lot of them. ๐Ÿ’…


๐Ÿฆ‹ bugprone-use-after-move

Okay so this one yells at you if you use an object after it's been moved. Classic. Example time:

std::string str = "Hello, world!\n";
std::vector<std::string> messages;
messages.emplace_back(std::move(str));
std::cout << str;

That last line? Yeah, it's gonna trigger a warning telling you str got used after it was moved. Not the vibe. ๐Ÿ˜ญ

But here's the good news โ€” the check stays quiet if you reinitialize the object after the move and before you use it. So this next one gets no warning:

messages.emplace_back(std::move(str));
str = "Greetings, stranger!\n";
std::cout << str;

The subsections below spell out exactly what counts as a move, a use, and a reinitialization. โœจ

The check also takes control flow into account โ€” a warning only fires if the use can actually be reached from the move. So this code produces no warning:

if (condition) {
  messages.emplace_back(std::move(str));
} else {
  std::cout << str;
}

But this one totally 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, iykyk.)

Sometimes though, the check can't tell that two branches are mutually exclusive. Like here (pretend 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 both the move and the use literally cannot both run. More formally: the analysis is flow-sensitive but not path-sensitive <https://en.wikipedia.org/wiki/Data-flow_analysis#Sensitivities>.

๐Ÿ™ˆ Silencing erroneous warnings

So you got a wrong warning? Rude. You can silence it by reinitializing the object after the move:

if (i == 1) {
  messages.emplace_back(std::move(str));
  str = "";
}
if (i == 2) {
  std::cout << str;
}

And if you don't wanna pay the cost of actually reinitializing the object, you can make a little dummy function that tricks the check into thinking the object got reinitialized:

template <class T>
void IS_INITIALIZED(T&) {}

Use it like this:

if (i == 1) {
  messages.emplace_back(std::move(str));
}
if (i == 2) {
  IS_INITIALIZED(str);
  std::cout << str;
}

No warning here, because passing the object to a function as a non-const pointer or reference counts as a reinitialization (peep the section Reinitialization below ๐Ÿ‘€).

๐ŸŒธ Unsequenced moves, uses, and reinitializations

Lots of the time, C++ makes zero promises about the order sub-expressions of a statement get evaluated in. So in code like this, it's genuinely not guaranteed whether the use happens before or after the move:

void f(int i, std::vector<int> v);
std::vector<int> v = { 1, 2, 3 };
f(v[1], std::move(v));

In that kind of situation, the check just notes that the use and the move are unsequenced. It's giving "we can't even". ๐Ÿซ 

The check also respects sequencing rules when reinitializations show up in the same statement as moves or uses. A reinitialization only counts as reinitializing a variable if it's guaranteed to be evaluated after the move and before the use.

๐Ÿ’œ Move

Right now the check only looks at calls to std::move on local variables or function parameters. It does not check moves of member variables or global variables. Not our problem, bestie.

Any call to std::move on a variable counts as causing a move of that variable โ€” even if the result of std::move never gets passed to an rvalue reference parameter.

Which 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 yes, that's intentional. Devs might call std::move on such a type fully expecting it to grow move semantics later. If that std::move has the potential to cause a use-after-move, we wanna warn about it even if the type hasn't implemented move semantics yet. Foresight, periodt.

Also: if the result of std::move is passed to an rvalue reference parameter, that always counts as causing a move โ€” even if the function consuming that parameter doesn't move from it, or only moves from it conditionally. Like here, the check assumes 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 assumes that last line causes a move, even though in this exact case it doesn't. Again โ€” intentional. ๐Ÿ‘‘

One special case though: a call to std::move inside a try_emplace call is conservatively assumed not to move. That's to dodge spurious warnings, since the check has no way to reason about the bool that try_emplace returns.

When it's analyzing the order in which moves, uses, and reinitializations happen (see the section Unsequenced moves, uses, and reinitializations above), the move is assumed to occur in whichever function the result of the std::move got passed to.

Oh and it also handles perfect-forwarding with std::forward, so this next bit also triggers a use-after-move warning:

void consume(int);

void f(int&& i) {
  consume(std::forward<int>(i));
  consume(std::forward<int>(i)); // use-after-move
}

โœจ Use

Any occurrence of the moved variable that isn't a reinitialization (see below) counts as a use.

The exception to this: objects of type std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional, and std::any. An exception to this are objects of type std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional, and std::any, which can be reinitialized via reset. For smart pointers specifically, the moved-from objects have a well-defined state of being nullptrs, and only operator*, operator-> and operator[] are considered bad accesses, since those would be dereferencing a nullptr. ๐Ÿ’€

And if multiple uses happen after a move, only the first one gets flagged.

๐ŸŽ€ Reinitialization

The check considers a variable to be reinitialized in these cases:

  • The variable shows up 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 the variable might be an out-parameter for that function.)
  • clear() or assign() is called on the variable, and the variable is 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.

Now โ€” if the variable in question is a struct and just one individual member variable of that struct gets written to, the check does not count that as a reinitialization. Even if eventually every member variable of the struct gets written to. 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 won't consider s reinitialized after that last line; instead, the line assigning to s.str gets flagged as a use-after-move. Intentional, because this reinit-a-struct-piece-by-piece pattern is straight-up error-prone. Like, add one more member variable to S and it's so easy to forget to reinitialize that new one too. Safer to just assign the entire struct in one go โ€” which also dodges the use-after-move warning. Slay. ๐Ÿ’–

๐ŸŒˆ Options

  • InvalidationFunctions โ€” a semicolon-separated list of regular expressions matching names of functions that cause their first arguments to be invalidated (think closing a handle, etc.). 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

Warns you if a function is a near miss to a virtual function from a base class โ€” meaning the name is very similar and the function signature is the same. ๐Ÿ‘€

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.

๐Ÿ’– cert-dcl51-cpp

The cert-dcl51-cpp check is an alias, bestie โ€” go peek at bugprone-reserved-identifier for the full story. ๐Ÿซถ


โœจ cert-dcl54-cpp

Yep, another alias situation: cert-dcl54-cpp is an alias, please see misc-new-delete-overloads for more information. ๐ŸŽ€


๐ŸŒธ cert-dcl58-cpp

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.

https://www.securecoding.cert.org/confluence/display/cplusplus/DCL58-CPP.+Do+not+modify+the+standard+namespaces


๐Ÿฆ‹ cert-dcl59-cpp

cert-dcl59-cpp is an alias, please see misc-anonymous-namespace-in-header for more information. ๐Ÿงš


๐Ÿ“ cert-env33-c

cert-env33-c 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().

https://www.securecoding.cert.org/confluence/display/c/ENV33-C.+Do+not+call+system()


๐Ÿ’œ cert-err09-cpp

cert-err09-cpp is an alias, please see misc-throw-by-value-catch-by-reference for more information. ๐ŸŒˆ

This check corresponds to the CERT C++ Coding Standard recommendation ERR09-CPP. Throw anonymous temporaries. But heads up, bestie: 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. ๐Ÿ˜ญ


๐Ÿ‘‘ cert-err33-c

Okay so this one is the return value police. It warns on unused function return values. A lot of the standard library functions hand you back a value that tells you whether the call actually worked, and ignoring that 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()
  • strtok()
  • strtok_s()
  • strtol()
  • strtold()
  • strtoll()
  • strtoumax()
  • strtoul()
  • strtoull()
  • strxfrm()
  • swprintf()
  • swprintf_s()
  • swscanf()
  • swscanf_s()
  • thrd_create()
  • thrd_detach()
  • thrd_join()
  • thrd_sleep()
  • time()
  • timespec_get()
  • tmpfile()
  • tmpfile_s()
  • tmpnam()
  • tmpnam_s()
  • tss_create()
  • tss_get()
  • tss_set()
  • ungetc()
  • ungetwc()
  • vfprintf()
  • vfprintf_s()
  • vfscanf()
  • vfscanf_s()
  • vfwprintf()
  • vfwprintf_s()
  • vfwscanf()
  • vfwscanf_s()
  • vprintf_s()
  • vscanf()
  • vscanf_s()
  • vsnprintf()
  • vsnprintf_s()
  • vsprintf()
  • vsprintf_s()
  • vsscanf()
  • vsscanf_s()
  • vswprintf()
  • vswprintf_s()
  • vswscanf()
  • vswscanf_s()
  • vwprintf_s()
  • vwscanf()
  • vwscanf_s()
  • wcrtomb()
  • wcschr()
  • wcsftime()
  • wcspbrk()
  • wcsrchr()
  • wcsrtombs()
  • wcsrtombs_s()
  • wcsstr()
  • wcstod()
  • wcstof()
  • wcstoimax()
  • wcstok()
  • wcstok_s()
  • wcstol()
  • wcstold()
  • wcstoll()
  • wcstombs()
  • wcstombs_s()
  • wcstoumax()
  • wcstoul()
  • wcstoull()
  • wcsxfrm()
  • wctob()
  • wctrans()
  • wctype()
  • wmemchr()
  • wprintf_s()
  • wscanf()
  • wscanf_s()

This check is an alias of check bugprone-unused-return-value with a fixed set of functions. ๐Ÿ’ซ

Suppressing issues by casting to void is enabled by default and can be disabled by setting the AllowCastToVoid option to false. ๐Ÿ”ฅ

The check corresponds to a part of CERT C Coding Standard rule ERR33-C. Detect and handle standard library errors.

https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors

The list of checked functions is taken from the rule, with following exception:

  • The check can not differentiate if a function is called with NULL argument. Therefore the following functions are not checked: mblen, mbrlen, mbrtowc, mbtowc, wctomb, wctomb_s

๐ŸŽ€ cert-err34-c

cert-err34-c is an alias, please see bugprone-unchecked-string-to-number-conversion for more information. โœจ


๐ŸŒˆ cert-err52-cpp

cert-err52-cpp 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().

https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=1834


๐Ÿฆ‹ cert-err58-cpp

cert-err58-cpp 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.

https://www.securecoding.cert.org/confluence/display/cplusplus/ERR58-CPP.+Handle+all+exceptions+thrown+before+main%28%29+begins+executing


๐Ÿ’œ cert-err60-cpp

cert-err60-cpp 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.

https://wiki.sei.cmu.edu/confluence/display/cplusplus/ERR60-CPP.+Exception+objects+must+be+nothrow+copy+constructible


โญ cert-err61-cpp

cert-err61-cpp is an alias, please see misc-throw-by-value-catch-by-reference for more information. ๐Ÿงš


๐Ÿ’– cert-exp42-c

cert-exp42-c is an alias, please see bugprone-suspicious-memory-comparison for more information. โœจ


๐Ÿ“ cert-fio38-c

cert-fio38-c 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.

https://www.securecoding.cert.org/confluence/display/c/FIO38-C.+Do+not+copy+a+FILE+object


๐Ÿ‘‘ cert-flp30-c

cert-flp30-c 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.

https://www.securecoding.cert.org/confluence/display/c/FLP30-C.+Do+not+use+floating-point+variables+as+loop+counters


๐Ÿฆ‹ cert-flp37-c

cert-flp37-c is an alias, please see bugprone-suspicious-memory-comparison for more information. ๐ŸŒธ


๐ŸŒˆ cert-int09-c

cert-int09-c is an alias, please see readability-enum-initial-value for more information. โœจ


๐Ÿ’œ cert-mem57-cpp

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.

https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM57-CPP.+Avoid+using+default+operator+new+for+over-aligned+types


๐Ÿ“ cert-msc24-c

cert-msc24-c is an alias, please see bugprone-unsafe-functions for more information. ๐Ÿ’ซ


โญ cert-msc30-c

cert-msc30-c 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.

https://wiki.sei.cmu.edu/confluence/display/c/MSC30-C.+Do+not+use+the+rand%28%29+function+for+generating+pseudorandom+numbers


๐Ÿงš cert-msc32-c

cert-msc32-c 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.

https://wiki.sei.cmu.edu/confluence/display/c/MSC32-C.+Properly+seed+pseudorandom+number+generators


๐Ÿ’– cert-msc33-c

cert-msc33-c is an alias, please see bugprone-unsafe-functions for more information. โœจ


๐ŸŒธ cert-msc50-cpp

cert-msc50-cpp 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.

https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC50-CPP.+Do+not+use+std%3A%3Arand%28%29+for+generating+pseudorandom+numbers


๐Ÿ‘‘ cert-msc51-cpp

cert-msc51-cpp 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.

https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC51-CPP.+Ensure+your+random+number+generator+is+properly+seeded

cert-msc54-cpp

Not a real check of its own, bestie โ€” cert-msc54-cpp is an alias โœจ so go read up on bugprone-signal-handler for the actual tea.


cert-oop11-cpp

Also just an alias, no cap โ€” cert-oop11-cpp points you at performance-move-constructor-init ๐Ÿ’… head there for the real info.

This one lines up with the CERT C++ Coding Standard recommendation OOP11-CPP. Do not copy-initialize members or base classes from a move constructor. But heads up, bestie: all of the CERT recommendations got yoinked from public view ๐Ÿ˜ญ so their whole justification for how this check behaves lives behind an account on their wiki. You need to log in to even read it. It's giving paywall energy fr.


cert-oop54-cpp

Alias alert ๐Ÿšจ cert-oop54-cpp is an alias, so please see bugprone-unhandled-self-assignment for more information.


cert-oop57-cpp

Yup, another alias โ€” cert-oop57-cpp 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 โ€” the link is here if you wanna vibe with it: https://wiki.sei.cmu.edu/confluence/display/cplusplus/OOP57-CPP.+Prefer+special+member+functions+and+overloaded+operators+to+C+Standard+Library+functions


cert-oop58-cpp

Alias again, sorry bestie ๐Ÿ’€ cert-oop58-cpp is an alias, please see bugprone-copy-constructor-mutates-argument for more information.


cert-pos44-c

cert-pos44-c is an alias โ€” please see bugprone-bad-signal-to-kill-thread for more information.


cert-pos47-c

cert-pos47-c is an alias โ€” please see concurrency-thread-canceltype-asynchronous for more information.


cert-sig30-c

cert-sig30-c is an alias, please see bugprone-signal-handler for more information.


cert-str34-c

cert-str34-c is an alias, please see bugprone-signed-char-misuse for more information.


clang-analyzer-core.BitwiseShift

Catches cases where a bitwise shift operation causes undefined behaviour ๐Ÿ˜ฑ

The clang-analyzer-core.BitwiseShift check is an alias, so please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-bitwiseshift for more information.


clang-analyzer-core.CallAndMessage

Checks for logical errors in function calls and Objective-C message expressions โ€” think uninitialized arguments, null function pointers, that whole situation ๐Ÿซ 

The clang-analyzer-core.CallAndMessage check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-callandmessage for more information.


clang-analyzer-core.DivideZero

Checks for division by zero ๐Ÿšซโž—

The clang-analyzer-core.DivideZero check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-dividezero for more information.


clang-analyzer-core.NonNullParamChecker

Checks for null pointers being 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 at https://clang.llvm.org/docs/analyzer/checkers.html#core-nonnullparamchecker for more information.


clang-analyzer-core.NullDereference

Checks for dereferences of null pointers ๐Ÿ’€ iconic but not in a good way.

The clang-analyzer-core.NullDereference check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-nulldereference for more information.


clang-analyzer-core.StackAddressEscape

Checks that addresses to stack memory don't escape the function ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ

The clang-analyzer-core.StackAddressEscape check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-stackaddressescape 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 at https://clang.llvm.org/docs/analyzer/checkers.html#core-undefinedbinaryoperatorresult for more information.


clang-analyzer-core.VLASize

Checks for declarations of VLA of undefined or zero size โœจ

The clang-analyzer-core.VLASize check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-vlasize for more information.


clang-analyzer-core.uninitialized.ArraySubscript

Checks for uninitialized values being used as array subscripts ๐Ÿ‘‘

The clang-analyzer-core.uninitialized.ArraySubscript check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-arraysubscript 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 at https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-assign for more information.


clang-analyzer-core.uninitialized.Branch

Checks for uninitialized values being used as branch conditions ๐ŸŒธ

The clang-analyzer-core.uninitialized.Branch check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-branch for more information.


clang-analyzer-core.uninitialized.CapturedBlockVariable

Checks for blocks that capture uninitialized values ๐Ÿงš

The clang-analyzer-core.uninitialized.CapturedBlockVariable check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-capturedblockvariable 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 at https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-newarraysize 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 at https://clang.llvm.org/docs/analyzer/checkers.html#core-uninitialized-undefreturn for more information.


clang-analyzer-cplusplus.ArrayDelete

Reports destructions of arrays of polymorphic objects that are destructed as their base class ๐Ÿ˜ฌ classic little oopsie.

The clang-analyzer-cplusplus.ArrayDelete check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-arraydelete for more information.


clang-analyzer-cplusplus.InnerPointer

Checks for inner pointers of C++ containers being used after re/deallocation ๐ŸŽ€

The clang-analyzer-cplusplus.InnerPointer check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-innerpointer for more information.


clang-analyzer-cplusplus.Move

Finds use-after-move bugs in C++ ๐Ÿƒโ€โ™€๏ธ

The clang-analyzer-cplusplus.Move check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-move for more information.


clang-analyzer-cplusplus.NewDelete

Checks for double-free and use-after-free problems, tracing memory managed by new/delete ๐Ÿ”ฅ

The clang-analyzer-cplusplus.NewDelete check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-newdelete for more information.


clang-analyzer-cplusplus.NewDeleteLeaks

Checks for memory leaks, tracing memory managed by new/delete ๐Ÿ’ง

The clang-analyzer-cplusplus.NewDeleteLeaks check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-newdeleteleaks for more information.


clang-analyzer-cplusplus.PlacementNew

Checks if default placement new is provided with pointers to sufficient storage capacity โญ

The clang-analyzer-cplusplus.PlacementNew check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-placementnew for more information.


clang-analyzer-cplusplus.SelfAssignment

Checks C++ copy and move assignment operators for self assignment ๐Ÿชž

The clang-analyzer-cplusplus.SelfAssignment check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-stringchecker for more information.


clang-analyzer-cplusplus.StringChecker

Checks C++ std::string bugs ๐Ÿงตโœจ

The clang-analyzer-cplusplus.StringChecker check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#cplusplus-stringchecker for more information.


clang-analyzer-deadcode.DeadStores

Checks for values stored to variables that are never read afterwards ๐Ÿชฆ

The clang-analyzer-deadcode.DeadStores check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#deadcode-deadstores for more information.


clang-analyzer-fuchsia.HandleChecker

A checker that detects leaks related to Fuchsia handles ๐Ÿ’œ

The clang-analyzer-fuchsia.HandleChecker check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#fuchsia-handlechecker for more information.


clang-analyzer-nullability.NullPassedToNonnull

Warns when a null pointer gets passed to a pointer which has a _Nonnull type ๐Ÿšจ

The clang-analyzer-nullability.NullPassedToNonnull check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullpassedtononnull for more information.


clang-analyzer-nullability.NullReturnedFromNonnull

Warns when a null pointer gets returned from a function that has a _Nonnull return type ๐Ÿ˜ญ

The clang-analyzer-nullability.NullReturnedFromNonnull check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullreturnedfromnonnull for more information.


clang-analyzer-nullability.NullableDereferenced

Warns when a nullable pointer gets dereferenced ๐Ÿ‘€

The clang-analyzer-nullability.NullableDereferenced check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullabledereferenced for more information.


clang-analyzer-nullability.NullablePassedToNonnull

Warns when a nullable pointer gets passed to a pointer which has a _Nonnull type ๐Ÿ’…

clang-analyzer-nullability.NullablePassedToNonnull

The clang-analyzer-nullability.NullablePassedToNonnull check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullablepassedtononnull> for more information.

clang-analyzer-nullability.NullableReturnedFromNonnull

Warns when a nullable pointer is returned from a function that has _Nonnull return type. ๐Ÿ’…

The clang-analyzer-nullability.NullableReturnedFromNonnull check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#nullability-nullablereturnedfromnonnull> for more information.

clang-analyzer-optin.core.EnumCastOutOfRange

Check integer to enumeration casts for out of range values. ๐ŸŽฏ

The clang-analyzer-optin.core.EnumCastOutOfRange check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-core-enumcastoutofrange> for more information.

clang-analyzer-optin.cplusplus.UninitializedObject

Reports uninitialized fields after object construction. ๐Ÿ˜ญ

The clang-analyzer-optin.cplusplus.UninitializedObject check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-cplusplus-uninitializedobject> for more information.

clang-analyzer-optin.cplusplus.VirtualCall

Check virtual function calls during construction/destruction. ๐Ÿฆ‹

The clang-analyzer-optin.cplusplus.VirtualCall check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-cplusplus-virtualcall> for more information.

clang-analyzer-optin.mpi.MPI-Checker

Checks MPI code. ๐Ÿ”ฅ

The clang-analyzer-optin.mpi.MPI-Checker check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-mpi-mpi-checker> for more information.

clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker

Check that NSLocalizedString macros include a comment for context. ๐ŸŽ€

The clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-osx-cocoa-localizability-emptylocalizationcontextchecker> for more information.

clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker

Warns about uses of non-localized NSStrings passed to UI methods expecting localized NSStrings. ๐ŸŒธ

The clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-osx-cocoa-localizability-nonlocalizedstringchecker> for more information.

clang-analyzer-optin.performance.GCDAntipattern

Check for performance anti-patterns when using Grand Central Dispatch. ๐Ÿ’€

The clang-analyzer-optin.performance.GCDAntipattern check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-performance-gcdantipattern> for more information.

clang-analyzer-optin.performance.Padding

Check for excessively padded structs. ๐Ÿซถ

The clang-analyzer-optin.performance.Padding check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-performance-padding> for more information.

clang-analyzer-optin.portability.UnixAPI

Finds implementation-defined behavior in UNIX/Posix functions. โญ

The clang-analyzer-optin.portability.UnixAPI check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-portability-unixapi> for more information.

clang-analyzer-optin.taint.TaintedAlloc

Check for memory allocations, where the size parameter might be a tainted (attacker controlled) value. ๐Ÿ™ˆ

The clang-analyzer-optin.taint.TaintedAlloc check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#optin-taint-taintedalloc> for more information.

clang-analyzer-osx.API

Check for proper uses of various Apple APIs. ๐Ÿ“

The clang-analyzer-osx.API check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-api> for more information.

clang-analyzer-osx.NumberObjectConversion

Check for erroneous conversions of objects representing numbers into numbers. ๐Ÿ”ข

The clang-analyzer-osx.NumberObjectConversion check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-numberobjectconversion> for more information.

clang-analyzer-osx.ObjCProperty

Check for proper uses of Objective-C properties. ๐Ÿ‘‘

The clang-analyzer-osx.ObjCProperty check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-objcproperty> for more information.

clang-analyzer-osx.SecKeychainAPI

Check for proper uses of Secure Keychain APIs. ๐Ÿ”

The clang-analyzer-osx.SecKeychainAPI check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-seckeychainapi> for more information.

clang-analyzer-osx.cocoa.AtSync

Check for nil pointers used as mutexes for @synchronized. ๐Ÿงš

The clang-analyzer-osx.cocoa.AtSync check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-atsync> for more information.

clang-analyzer-osx.cocoa.AutoreleaseWrite

Warn about potentially crashing writes to autoreleasing objects from different autoreleasing pools in Objective-C. ๐Ÿ’ฅ

The clang-analyzer-osx.cocoa.AutoreleaseWrite check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-autoreleasewrite> for more information.

clang-analyzer-osx.cocoa.ClassRelease

Check for sending 'retain', 'release', or 'autorelease' directly to a Class. ๐ŸŒˆ

The clang-analyzer-osx.cocoa.ClassRelease check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-classrelease> for more information.

clang-analyzer-osx.cocoa.Dealloc

Warn about Objective-C classes that lack a correct implementation of -dealloc. ๐Ÿ’œ

The clang-analyzer-osx.cocoa.Dealloc check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-dealloc> for more information.

clang-analyzer-osx.cocoa.IncompatibleMethodTypes

Warn about Objective-C method signatures with type incompatibilities. โœจ

The clang-analyzer-osx.cocoa.IncompatibleMethodTypes check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-incompatiblemethodtypes> for more information.

clang-analyzer-osx.cocoa.Loops

Improved modeling of loops using Cocoa collection types. ๐Ÿ”„

The clang-analyzer-osx.cocoa.Loops check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-loops> for more information.

clang-analyzer-osx.cocoa.MissingSuperCall

Warn about Objective-C methods that lack a necessary call to super. ๐Ÿซข

The clang-analyzer-osx.cocoa.MissingSuperCall check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-missingsupercall> for more information.

clang-analyzer-osx.cocoa.NSAutoreleasePool

Warn for suboptimal uses of NSAutoreleasePool in Objective-C GC mode. ๐ŸŒŠ

The clang-analyzer-osx.cocoa.NSAutoreleasePool check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nsautoreleasepool> for more information.

clang-analyzer-osx.cocoa.NSError

Check usage of NSError** parameters. ๐Ÿ˜ต

The clang-analyzer-osx.cocoa.NSError check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nserror> for more information.

clang-analyzer-osx.cocoa.NilArg

Check for prohibited nil arguments to ObjC method calls. ๐Ÿšซ

The clang-analyzer-osx.cocoa.NilArg check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nilarg> for more information.

clang-analyzer-osx.cocoa.NonNilReturnValue

Model the APIs that are guaranteed to return a non-nil value. โœ…

The clang-analyzer-osx.cocoa.NonNilReturnValue check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-nonnilreturnvalue> for more information.

clang-analyzer-osx.cocoa.ObjCGenerics

Check for type errors when using Objective-C generics. ๐Ÿงฉ

The clang-analyzer-osx.cocoa.ObjCGenerics check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-objcgenerics> for more information.

clang-analyzer-osx.cocoa.RetainCount

Check for leaks and improper reference count management. ๐Ÿ’ง

The clang-analyzer-osx.cocoa.RetainCount check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-retaincount> for more information.

clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak

Check for leaked memory in autorelease pools that will never be drained. ๐Ÿ•ณ๏ธ

The clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-runloopautoreleaseleak> for more information.

clang-analyzer-osx.cocoa.SelfInit

Check that 'self' is properly initialized inside an initializer method. ๐Ÿ’–

The clang-analyzer-osx.cocoa.SelfInit check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-selfinit> for more information.

clang-analyzer-osx.cocoa.SuperDealloc

Warn about improper use of '[super dealloc]' in Objective-C. โš ๏ธ

The clang-analyzer-osx.cocoa.SuperDealloc check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-superdealloc> for more information.

clang-analyzer-osx.cocoa.UnusedIvars

Warn about private ivars that are never used. ๐Ÿ—‘๏ธ

The clang-analyzer-osx.cocoa.UnusedIvars check is an alias, please see Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-unusedivars> for more information.

clang-analyzer-osx.cocoa.VariadicMethodTypes</p>

Okay so bestie, this one is on watch duty for when you pass non-Objective-C types into variadic collection initialization methods โ€” y'know, the ones that are literally only expecting Objective-C types? Yeah. That. It's giving "wrong type, wrong party" ๐Ÿ’…

The clang-analyzer-osx.cocoa.VariadicMethodTypes check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#osx-cocoa-variadicmethodtypes for more information.


clang-analyzer-osx.coreFoundation.CFError</p>

This one keeps an eye on how you're using your CFErrorRef* parameters. Usage check, no cap. ๐Ÿฆ‹

The clang-analyzer-osx.coreFoundation.CFError check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cferror for more information.


clang-analyzer-osx.coreFoundation.CFNumber</p>

Watching for proper uses of CFNumber APIs. Proper being the key word here, bestie โ€” no chaotic CFNumber behaviour allowed โœจ

The clang-analyzer-osx.coreFoundation.CFNumber check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cfnumber for more information.


clang-analyzer-osx.coreFoundation.CFRetainRelease</p>

She's checking for null arguments to CFRetain/CFRelease/CFMakeCollectable. Passing null to these? Not the vibe. Absolutely not the moment. ๐Ÿ’€

The clang-analyzer-osx.coreFoundation.CFRetainRelease check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-cfretainrelease for more information.


clang-analyzer-osx.coreFoundation.containers.OutOfBounds</p>

Checks for index out-of-bounds moments when you're using the 'CFArray' API. Like bestie, the array has edges, please respect them ๐Ÿ˜ญ

The clang-analyzer-osx.coreFoundation.containers.OutOfBounds check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-containers-outofbounds for more information.


clang-analyzer-osx.coreFoundation.containers.PointerSizedValues</p>

Warns you if 'CFArray', 'CFDictionary', or 'CFSet' get created with non-pointer-size values. Like. They need pointer-sized values. It's literally in the name, iykyk ๐Ÿ‘‘

The clang-analyzer-osx.coreFoundation.containers.PointerSizedValues check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#osx-corefoundation-containers-pointersizedvalues for more information.


clang-analyzer-security.FloatLoopCounter</p>

Warns you when you're using a floating point value as a loop counter. Certified FLP30-C, FLP30-CPP behaviour (that's a CERT rule, bestie, it's serious). Floating point loop counters are the villain origin story of infinite loops, fr fr ๐Ÿ™ˆ

The clang-analyzer-security.FloatLoopCounter check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-floatloopcounter for more information.


clang-analyzer-security.PutenvStackArray</p>

This one finds calls to the putenv function that pass a pointer to a stack-allocated (automatic) array as the argument. And here's the tea, bestie: putenv does not copy the passed string โ€” only a pointer to the data is stored, and that data can be read even by other threads. Meanwhile the content of a stack-allocated array is likely to get overwritten once you exit the function. So likeโ€ฆ dangling city. Population: your string. ๐Ÿซ 

The clang-analyzer-security.PutenvStackArray check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-putenvstackarray-c for more information.


clang-analyzer-security.SetgidSetuidOrder</p>

Okay so this checker is looking for sequences of setuid(getuid()) and setgid(getgid()) calls โ€” in this order. If it finds such a sequence and there's no other privilege-changing function call in between (seteuid, setreuid, setresuid and the GID versions of these), a warning gets generated. โœจ

And a lil note, because precision matters: 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. Sneaky little shortcut and it will not catch it. ๐Ÿ’…

The clang-analyzer-security.SetgidSetuidOrder check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-setgidsetuidorder-c for more information.


clang-analyzer-security.cert.env.InvalidPtr</p>

Finds usages of possibly invalidated pointers. That's it, that's the whole job. And honestly? Iconic of it. ๐ŸŽ€

The clang-analyzer-security.cert.env.InvalidPtr check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-cert-env-invalidptr for more information.


clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling</p>

Warns on uses of unsecure or deprecated buffer manipulating functions. Yes bestie, the whole "this function has been cancelled" lineup. ๐Ÿšซ

The clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-deprecatedorunsafebufferhandling for more information.


clang-analyzer-security.insecureAPI.UncheckedReturn</p>

Warns on uses of functions whose return values must be always checked. Always, bestie. Not sometimes. Not when you feel like it. Always. Check your returns, check your returns, check your returns ๐Ÿ‘

The clang-analyzer-security.insecureAPI.UncheckedReturn check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-uncheckedreturn for more information.


clang-analyzer-security.insecureAPI.bcmp</p>

Warns on uses of the 'bcmp' function. Yeah. That one. ๐Ÿ’€

The clang-analyzer-security.insecureAPI.bcmp check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bcmp for more information.


clang-analyzer-security.insecureAPI.bcopy</p>

Warns on uses of the 'bcopy' function. Its sibling. Also cancelled. ๐Ÿ™ˆ

The clang-analyzer-security.insecureAPI.bcopy check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bcopy for more information.


clang-analyzer-security.insecureAPI.bzero</p>

Warns on uses of the 'bzero' function. Third sibling, same energy. โœจ

The clang-analyzer-security.insecureAPI.bzero check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-bzero for more information.


clang-analyzer-security.insecureAPI.decodeValueOfObjCType</p>

Warns on uses of the '-decodeValueOfObjCType:at:' method. That whole selector, yes ma'am. ๐Ÿฆ‹

The clang-analyzer-security.insecureAPI.decodeValueOfObjCType check is an alias, please see Clang Static Analyzer security.insecureAPI.decodeValueOfObjCType at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-decodevalueofobjctype for more information.


clang-analyzer-security.insecureAPI.getpw</p>

Warns on uses of the 'getpw' function. Bestie. Just don't. ๐Ÿ’…

The clang-analyzer-security.insecureAPI.getpw check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-getpw for more information.


clang-analyzer-security.insecureAPI.gets</p>

Warns on uses of the 'gets' function. THE gets. The legendary one. The one that's such a problem it got removed from the language. If you're seeing this warning, we need to talk. ๐Ÿ˜ญ

The clang-analyzer-security.insecureAPI.gets check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-gets for more information.


clang-analyzer-security.insecureAPI.mkstemp</p>

Warns when 'mkstemp' is passed fewer than 6 X's in the format string. Count your X's, bestie. Six minimum. It's giving template, not suggestion. ๐Ÿ”ฅ

The clang-analyzer-security.insecureAPI.mkstemp check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-mkstemp for more information.


clang-analyzer-security.insecureAPI.mktemp</p>

Warns on uses of the 'mktemp' function. Notice how it's the sibling of mkstemp but missing the s? Yeah, that's the whole problem. ๐Ÿšฉ

The clang-analyzer-security.insecureAPI.mktemp check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-mktemp for more information.


clang-analyzer-security.insecureAPI.rand</p>

Warns on uses of the 'rand', 'random', and related functions. For when you need randomness randomness and not "the same sequence every time, but make it fashion". โญ

The clang-analyzer-security.insecureAPI.rand check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-rand for more information.


clang-analyzer-security.insecureAPI.strcpy</p>

Warns on uses of the 'strcpy' and 'strcat' functions. The classic duo. The "how big is the buffer, actually?" show. ๐ŸŽ€

The clang-analyzer-security.insecureAPI.strcpy check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-strcpy for more information.


clang-analyzer-security.insecureAPI.vfork</p>

Warns on uses of the 'vfork' function. Yes bestie, the one that shares memory with its parent. Fun! Terrifying! ๐Ÿซถ

The clang-analyzer-security.insecureAPI.vfork check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#security-insecureapi-vfork for more information.


clang-analyzer-unix.API</p>

Checks calls to various UNIX/Posix functions. The whole gang, one checker. ๐Ÿ’™

The clang-analyzer-unix.API check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-api for more information.


clang-analyzer-unix.BlockInCriticalSection</p>

Checks for calls to blocking functions inside a critical section. Blocking in the critical section? Not on my watch. ๐ŸŒธ

The clang-analyzer-unix.BlockInCriticalSection check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-blockincriticalsection for more information.


clang-analyzer-unix.Errno</p>

Checks for improper use of 'errno'. And errno loves to be used improperly, so honestly, this check is doing the lord's work. โœจ

The clang-analyzer-unix.Errno check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-errno for more information.


clang-analyzer-unix.Malloc</p>

THE ONE. Checks for memory leaks, double free, and use-after-free problems โ€” and it traces memory managed by malloc()/free(). This is the main character of memory checking, no cap. ๐Ÿ‘‘

The clang-analyzer-unix.Malloc check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-malloc for more information.


clang-analyzer-unix.MallocSizeof</p>

Checks for dubious malloc arguments involving sizeof. Y'know, the sizeof(pointer) situation. The one that compiles beautifully and does nothing you wanted. ๐Ÿซ 

The clang-analyzer-unix.MallocSizeof check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-mallocsizeof for more information.


clang-analyzer-unix.MismatchedDeallocator</p>

Checks for mismatched deallocators. You allocated with one thing and freed with another. The relationship is not working out, bestie. ๐Ÿ’”

The clang-analyzer-unix.MismatchedDeallocator check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-mismatcheddeallocator for more information.


clang-analyzer-unix.StdCLibraryFunctions</p>

Checks for invalid arguments of C standard library functions, and applies relations between arguments and return value. Very thorough. Very attentive. Very "I read the whole spec". ๐ŸŒˆ

The clang-analyzer-unix.StdCLibraryFunctions check is an alias, please see Clang Static Analyzer Available Checkers at https://clang.llvm.org/docs/analyzer/checkers.html#unix-stdclibraryfunctions for more information.


clang-analyzer-unix.Stream</p>

Checks stream handling functions. Streams! Handle them. Please. ๐ŸŒŠ

clang-analyzer-unix.Stream

The clang-analyzer-unix.Stream check is literally just an alias bestie, so slide over to Clang Static Analyzer Available Checkers https://clang.llvm.org/docs/analyzer/checkers.html#unix-stream for the full tea. ๐Ÿ’–


clang-analyzer-unix.Vfork

Checks that you're using vfork the way it was meant to be used, fr fr. ๐Ÿ‘‘

The clang-analyzer-unix.Vfork check is an alias, please see Clang Static Analyzer Available Checkers https://clang.llvm.org/docs/analyzer/checkers.html#unix-vfork for more information.


clang-analyzer-unix.cstring.BadSizeArg

Checks the size argument you're handing to C string functions, looking for the common erroneous patterns. ๐Ÿ•ต๏ธโ€โ™€๏ธ

The clang-analyzer-unix.cstring.BadSizeArg check is an alias, please see Clang Static Analyzer Available Checkers https://clang.llvm.org/docs/analyzer/checkers.html#unix-cstring-badsizearg for more information.


clang-analyzer-unix.cstring.NullArg

Checks for null pointers being passed as arguments to C string functions. Not the vibe. ๐Ÿ˜ญ

The clang-analyzer-unix.cstring.NullArg check is an alias, please see Clang Static Analyzer Available Checkers https://clang.llvm.org/docs/analyzer/checkers.html#unix-cstring-nullarg for more information.


clang-analyzer-webkit.NoUncountedMemberChecker

Checks for no uncounted member variables. ๐ŸŽ€

The clang-analyzer-webkit.NoUncountedMemberChecker check is an alias, please see Clang Static Analyzer Available Checkers https://clang.llvm.org/docs/analyzer/checkers.html#webkit-nouncountedmemberchecker for more information.


clang-analyzer-webkit.RefCntblBaseVirtualDtor

Checks for any ref-countable base class having a virtual destructor. ๐Ÿ’…

The clang-analyzer-webkit.RefCntblBaseVirtualDtor check is an alias, please see Clang Static Analyzer Available Checkers https://clang.llvm.org/docs/analyzer/checkers.html#webkit-refcntblbasevirtualdtor for more information.


clang-analyzer-webkit.UncountedLambdaCapturesChecker

Checks uncounted lambda captures. ๐ŸŒธ

The clang-analyzer-webkit.UncountedLambdaCapturesChecker check is an alias, please see Clang Static Analyzer Available Checkers https://clang.llvm.org/docs/analyzer/checkers.html#webkit-uncountedlambdacaptureschecker for more information.


concurrency-mt-unsafe

Okay so this one checks for some thread-unsafe functions against a black list of known-to-be-unsafe functions. Usually they're out here accessing static variables with zero synchronization (looking at you, gmtime(3)) or utilizing signals in a racy way. The set of functions to check is specified with the FunctionSet option. ๐Ÿงต๐Ÿ’€

Note that using some thread-unsafe functions may <highkey>still be valid</highkey> in concurrent programming if only a single thread is used (e.g. setenv(3)), however, some functions may track a state in global variables which would be clobbered by subsequent (non-parallel, but concurrent) calls to a related function. E.g. the following code suffers from unprotected accesses to a global state:
// getnetent(3) maintains global state with DB connection, etc.
// If a concurrent green thread calls getnetent(3), the global state is corrupted.
netent = getnetent();
yield();
netent = getnetent();

Examples:

tm = gmtime(timep); // uses a global buffer

sleep(1); // implementation may use SIGALRM

Options โœจ

  • FunctionSet โ€” Specifies which functions 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 https://wiki.sei.cmu.edu/confluence/display/c/POS47-C.+Do+not+use+threads+that+can+be+canceled+asynchronously.

cert-pos47-c redirects here as an alias of this check. ๐Ÿฆ‹


cppcoreguidelines-avoid-c-arrays

The cppcoreguidelines-avoid-c-arrays check is an alias, please see modernize-avoid-c-arrays <> for more information. ๐Ÿ’œ


cppcoreguidelines-avoid-capturing-lambda-coroutines

Flags C++20 coroutine lambdas with non-empty capture lists that may cause use-after-free errors and suggests avoiding captures or ensuring the lambda closure object has a guaranteed lifetime. ๐Ÿ˜ญ

This check implements CP.51 https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rcoro-capture from the C++ Core Guidelines.

Using coroutine lambdas with non-empty capture lists can be risky, as capturing variables can lead to accessing freed memory after the first suspension point. This issue can occur even with refcounted smart pointers and copyable types. When a lambda expression creates a coroutine, it results in a closure object with storage, which is often on the stack and will eventually go out of scope. When the closure object goes out of scope, its captures also go out of scope. While normal lambdas finish executing before this happens, coroutine lambdas may resume from suspension after the closure object has been destructed, resulting in use-after-free memory access for all captures. ๐Ÿ’€

Consider the following example:

int value = get_value();
std::shared_ptr<Foo> sharedFoo = get_foo();
{
    const auto lambda = [value, sharedFoo]() -> std::future<void>
    {
        co_await something();
        // "sharedFoo" and "value" have already been destroyed
        // the "shared" pointer didn't accomplish anything
    };
    lambda();
} // the lambda closure object has now gone out of scope

In this example, the lambda object is defined with two captures: value and sharedFoo. When lambda() is called, the lambda object is created on the stack, and the captures are copied into the closure object. When the coroutine is suspended, the lambda object goes out of scope, and the closure object is destroyed. When the coroutine is resumed, the captured variables may have been destroyed, resulting in use-after-free bugs. ๐Ÿ™ˆ

In conclusion, the use of coroutine lambdas with non-empty capture lists can lead to use-after-free errors when resuming the coroutine after the closure object has been destroyed. This check helps prevent such errors by flagging C++20 coroutine lambdas with non-empty capture lists and suggesting avoiding captures or ensuring the lambda closure object has a guaranteed lifetime.

Following these guidelines can help ensure the safe and reliable use of coroutine lambdas in C++ code. โœจ


cppcoreguidelines-avoid-const-or-ref-data-members

This check warns when structs or classes that are copyable or movable, and have const-qualified or reference (lvalue or rvalue) data members. Having such members is <highkey>rarely useful</highkey>, and makes the class only copy-constructible but not copy-assignable. ๐ŸŽ€

Examples:

// Bad, const-qualified member
struct Const {
  const int x;
}

// Good:
class Foo {
 public:
  int get() const { return x; }
 private:
  int x;
};

// Bad, lvalue reference member
struct Ref {
  int& x;
};

// Good:
struct Foo {
  int* x;
  std::unique_ptr<int> x;
  std::shared_ptr<int> x;
  gsl::not_null<int*> x;
};

// Bad, rvalue reference member
struct RefRef {
  int&& x;
};

This check implements C.12 https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rc-constref from the C++ Core Guidelines.

Further reading: Data members: Never const https://quuxplusone.github.io/blog/2022/01/23/dont-const-all-the-things/#data-members-never-const. ๐Ÿ“š


cppcoreguidelines-avoid-do-while

Warns when you're out here using do-while loops. They're less readable than plain while loops, since the termination condition is at the end and the condition is not checked prior to the first iteration. This can lead to subtle bugs. ๐Ÿ›

This check implements ES.75 https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#res-do from the C++ Core Guidelines.

Examples:

int x;
do {
    std::cin >> x;
    // ...
} while (x < 0);

Options โœจ

  • IgnoreMacros โ€” Ignore the check when analyzing macros. This is useful for safely defining function-like macros:
#define FOO_BAR(x) \
do { \
  foo(x); \
  bar(x); \
} while(0)

Defaults to false.


cppcoreguidelines-avoid-goto

The usage of goto for control flow is error prone and should be replaced with looping constructs. Only forward jumps in nested loops are accepted. ๐Ÿ‘‘

This check implements ES.76 https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es76-avoid-goto from the C++ Core Guidelines and 6.3.1 https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements from High Integrity C++ Coding Standard.

For more information on why to avoid programming with goto you can read the famous paper A Case against the GO TO Statement. https://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF.

The check diagnoses goto for backward jumps in every language mode. These should be replaced with C/C++ looping constructs.

// Bad, handwritten for loop.
int i = 0;
// Jump label for the loop
loop_start:
do_some_operation();

if (i < 100) {
  ++i;
  goto loop_start;
}

// Better
for(int i = 0; i < 100; ++i)
  do_some_operation();

Modern C++ needs goto only to jump out of nested loops. ๐Ÿ’…

for(int i = 0; i < 100; ++i) {
  for(int j = 0; j < 100; ++j) {
    if (i * j > 500)
      goto early_exit;
  }
}

early_exit:
some_operation();

All other uses of goto are diagnosed in C++. โœจ

โœจ Options โœจ

  • IgnoreMacros โ€” set this to true and the check will not warn when a goto statement gets expanded out of a macro. Default is false. ๐Ÿ’…

๐ŸŒˆ cppcoreguidelines-avoid-magic-numbers ๐ŸŒˆ

Bestie, this one is just an alias ๐Ÿซถ โ€” it's the same check as readability-magic-numbers <>, so go read that one for the tea.

๐ŸŒธ cppcoreguidelines-avoid-non-const-global-variables ๐ŸŒธ

Not me catching every non-const global variable out here ๐Ÿ‘€ โ€” this check enforces rule I.2 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i2-avoid-non-const-global-variables) of the C++ Core Guidelines. And since rule R.6 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rr-global) is literally a duplicate of rule I.2, this check covers that rule too. Two birds, one slay. ๐Ÿฆ‹

char a;  // Warns!
const char b =  0;

namespace some_namespace
{
    char c;  // Warns!
    const char d = 0;
}

char * c_ptr1 = &some_namespace::c;  // Warns!
char *const c_const_ptr = &some_namespace::c;  // Warns!
char & c_reference = some_namespace::c;  // Warns!

class Foo  // No Warnings inside Foo, only namespace scope is covered
{
public:
    char e = 0;
    const char f = 0;
protected:
    char g = 0;
private:
    char h = 0;
};

So the variables a, c, c_ptr1, c_const_ptr and c_reference are all gonna generate warnings, because each one is either a non-const globally accessible variable, or a pointer/reference giving global access to non-const data, or honestly both. It's giving "exposed" ๐Ÿ’€

๐ŸŽ€ Options ๐ŸŽ€

  • AllowInternalLinkage โ€” when set to true, static non-const variables and variables sitting inside 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. โœจ

๐Ÿ’œ cppcoreguidelines-avoid-reference-coroutine-parameters ๐Ÿ’œ

This one warns when a coroutine accepts reference parameters, bestie. Why? Because after a coroutine suspend point, those references could be dangling and no longer valid ๐Ÿ˜ญ. Instead, pass parameters as values. Periodt.

Examples:

std::future<int> someCoroutine(int& val) {
  co_await ...;
  // When the coroutine is resumed, 'val' might no longer be valid.
  if (val) ...
}

This check implements CP.53 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rcoro-reference-parameters) from the C++ Core Guidelines. ๐Ÿ‘‘

๐Ÿฆ‹ cppcoreguidelines-c-copy-assignment-signature ๐Ÿฆ‹

Another alias, bestie โ€” this check is the same as misc-unconventional-assign-operator <>, so check that page for the full story.

โญ cppcoreguidelines-explicit-virtual-functions โญ

Alias alert ๐Ÿšจ โ€” this check is the same as modernize-use-override <>, go read that one.

๐ŸŒธ cppcoreguidelines-init-variables ๐ŸŒธ

This check looks for local variables declared with no initial value โ€” because those can lead to unexpected behavior if there's a code path that reads the variable before assigning to it. Not the vibe. ๐Ÿ™ˆ

It's part of the Type safety (Type.5) (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-init) profile and ES.20 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#res-always) from the C++ Core Guidelines.

Only integers, booleans, floats, doubles and pointers get checked. The fix option initializes all detected values with the value of zero โ€” with one exception: float and double types, which get initialized to NaN. ๐Ÿ’…

So like, a function that looks like this:

void function() {
  int x;
  char *txt;
  double d;

  // Rest of the function.
}

Would be rewritten to look like this:

#include <math.h>

void function() {
  int x = 0;
  char *txt = nullptr;
  double d = NAN;

  // Rest of the function.
}

It does warn 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 โ€” which means they can lead to order-of-initialization problems. ๐Ÿ˜ตโ€๐Ÿ’ซ Chaos, honestly.

It implements I.22 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#ri-global-init) from the C++ Core Guidelines.

Ngl, heads up: currently this does not flag calls to non-constexpr functions, and therefore globals could still be accessed from functions themselves. iykyk. ๐Ÿซถ

๐ŸŒˆ cppcoreguidelines-macro-to-enum ๐ŸŒˆ

Alias, bestie โ€” same check as modernize-macro-to-enum <>, go peep that one for details.

๐Ÿ’œ cppcoreguidelines-macro-usage ๐Ÿ’œ

This check finds macro usage that's considered problematic, because better language constructs exist for the task. The macro era is over, we're healing. โœจ

The relevant sections in the C++ Core Guidelines are ES.31 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es31-dont-use-macros-for-constants-or-functions) and ES.32 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es32-use-all_caps-for-all-macro-names).

Examples:

#define C 0
#define F1(x, y) ((a) > (b) ? (a) : (b))
#define F2(...) (__VA_ARGS__)
#define F3(x, y) x##y
#define COMMA ,
#define NORETURN [[noreturn]]
#define DEPRECATED attribute((deprecated))
#if LIB_EXPORTS
#define DLLEXPORTS __declspec(dllexport)
#else
#define DLLEXPORTS __declspec(dllimport)
#endif

results in the following warnings:

4 warnings generated.
test.cpp:1:9: warning: macro 'C' used to declare a constant; consider using a 'constexpr' constant [cppcoreguidelines-macro-usage]
#define C 0
        ^
test.cpp:2:9: warning: function-like macro 'F1' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define F1(x, y) ((a) > (b) ? (a) : (b))
        ^
test.cpp:3:9: warning: variadic macro 'F2' used; consider using a 'constexpr' variadic template function [cppcoreguidelines-macro-usage]
#define F2(...) (__VA_ARGS__)
        ^

๐ŸŽ€ Options ๐ŸŽ€

  • AllowedRegexp โ€” a regular expression to filter allowed macros. For 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. 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. ๐Ÿšฉ

Here's the thing: by-value capture defaults in member functions can be misleading about whether data members are captured by value or reference. Why? 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 basically 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 "lie". ๐Ÿ˜ญ

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 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f54-when-writing-a-lambda-that-captures-this-or-any-class-data-member-dont-use--default-capture) from the C++ Core Guidelines. ๐Ÿ‘‘

โญ cppcoreguidelines-missing-std-forward โญ

Warns when a forwarding reference parameter is not forwarded inside the function body. Bestie, forward it. ๐Ÿ™

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 (http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rf-forward) from the C++ Core Guidelines.

๐ŸŒˆ cppcoreguidelines-narrowing-conversions ๐ŸŒˆ

This check implements part of ES.46 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es46-avoid-lossy-narrowing-truncating-arithmetic-conversions) from the C++ Core Guidelines.

And plot twist โ€” cppcoreguidelines-narrowing-conversions is an alias, so please see bugprone-narrowing-conversions <> for more info. ๐Ÿซถ

๐Ÿ’œ 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. We love RAII in this house. ๐Ÿ’…

Furthermore, it can be configured to check against a user-specified list of functions that are used for memory management (e.g. posix_memalign()).

This check implements R.10 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rr-mallocfree) from the C++ Core Guidelines. ๐Ÿ‘‘

There is no attempt made to provide fix-it hints here, bestie โ€” because manual resource management isn't easily transformed automatically into RAII. It's giving "you're gonna have to do this one yourself." ๐Ÿ˜ญ

// 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. Bestie. Listen. ๐Ÿ‘‘

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

Instead, locks should be released before suspending a coroutine. That's the whole vibe.

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 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rcoro-locks) from the C++ Core Guidelines. ๐Ÿ’…


๐ŸŽ€ cppcoreguidelines-noexcept-destructor

This check implements C.37 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c37-make-destructors-noexcept) from the C++ Core Guidelines.

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 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c66-make-move-operations-noexcept) from the C++ Core Guidelines.

The cppcoreguidelines-noexcept-move-operations check is an alias, please see performance-noexcept-move-constructor for more information. โœจ


๐ŸŽ€ cppcoreguidelines-noexcept-swap

This check implements C.83 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c83-for-value-like-types-consider-providing-a-noexcept-swap-function), C.84 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c84-a-swap-function-must-not-fail) and C.85 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c85-make-swap-noexcept) from the C++ Core Guidelines. Three-for-one, iconic behavior honestly. ๐Ÿ‘‘

The cppcoreguidelines-noexcept-swap check is an alias, please see performance-noexcept-swap for more information. โœจ


๐ŸŽ€ cppcoreguidelines-non-private-member-variables-in-classes

The cppcoreguidelines-non-private-member-variables-in-classes check is an alias, please see misc-non-private-member-variables-in-classes for more information. โœจ


๐Ÿ‘‘ cppcoreguidelines-owning-memory

This check implements the type-based semantics of gsl::owner<T*>, which allows static analysis on code that uses raw pointers to handle resources like dynamic memory, but won't introduce RAII concepts. No cap. ๐Ÿ’…

This check implements I.11 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i11-never-transfer-ownership-by-a-raw-pointer-t-or-reference-t), C.33 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c33-if-a-class-has-an-owning-pointer-member-define-a-destructor), R.3 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r3-a-raw-pointer-a-t-is-non-owning) and GSL.Views (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#ss-views) from the C++ Core Guidelines. The definition of a gsl::owner<T*> is straight forward ๐Ÿซถ

namespace gsl { template <typename T> owner = T; }

It is therefore simple to introduce the owner even without using an implementation of the Guideline Support Library (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#s-gsl).

All checks are purely type based and not (yet) flow sensitive. Just so we're clear. ๐ŸŒธ

The following examples will demonstrate the correct and incorrect initializations of owners, assignment is handled the same way. Note that both new and malloc()-like resource functions are considered to produce resources.

// Creating an owner with factory functions is checked.
gsl::owner<int*> function_that_returns_owner() { return gsl::owner<int*>(new int(42)); }

// Dynamic memory must be assigned to an owner
int* Something = new int(42); // BAD, will be caught
gsl::owner<int*> Owner = new int(42); // Good
gsl::owner<int*> Owner = new int[42]; // Good as well

// Returned owner must be assigned to an owner
int* Something = function_that_returns_owner(); // Bad, factory function
gsl::owner<int*> Owner = function_that_returns_owner(); // Good, result lands in owner

// Something not a resource or owner should not be assigned to owners
int Stack = 42;
gsl::owner<int*> Owned = &Stack; // Bad, not a resource assigned

In the case of dynamic memory as resource, only gsl::owner<T*> variables are allowed to be deleted. ๐Ÿ”ฅ

// Example Bad, non-owner as resource handle, will be caught.
int* NonOwner = new int(42); // First warning here, since new must land in an owner
delete NonOwner; // Second warning here, since only owners are allowed to be deleted

// Example Good, Ownership correctly stated
gsl::owner<int*> Owner = new int(42); // Good
delete Owner; // Good as well, statically enforced, that only owners get deleted

The check will furthermore ensure, that functions, 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. Therefore using gsl::owner<T*> in a heavy templated code base might lead to false positives. Not the vibe, but it is what it is. ๐Ÿ™ˆ

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 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. Two wins, zero losses, we love to see it. ๐Ÿ’…

Class-member assignments inside a control statement or following the first control statement are ignored. ๐ŸŽ€

This check is the living embodiment of C.49 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c49-prefer-initialization-to-assignment-in-constructors> from the C++ Core Guidelines. ๐Ÿ’–

Real talk though bestie โ€” this check does not enforce rule C.48 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c48-prefer-in-class-initializers-to-member-initializers-in-constructors-for-constant-initializers> from the C++ Core Guidelines. That's a whole different vibe, and for that one you want the check modernize-use-default-member-init. ๐Ÿซถ

Example 1

class C {
  int n;
  int m;
public:
  C() {
    n = 1; // Literal in default constructor
    if (dice())
      return;
    m = 1;
  }
};

Okay so here's the tea: n can absolutely be initialized in the constructor initializer list. But m? Nope, not invited. Because m's initialization comes after a control statement (that if), so it can't join the initializer-list party. ๐Ÿ˜ญ

Slay, here's the glow-up:

class C {
  int n;
  int m;
public:
  C(): n(1) {
    if (dice())
      return;
    m = 1;
  }
};

Example 2

class C {
  int n;
  int m;
public:
  C(int nn, int mm) {
    n = nn; // Neither default constructor nor literal
    if (dice())
      return;
    m = mm;
  }
};

Same energy, same story: n can go in the constructor initializer list, m cannot, because m's initialization is following a control statement (the if, again, iconic behavior tbh).

C(int nn, int mm) : n(nn) {
  if (dice())
    return;
  m = mm;
}

cppcoreguidelines-pro-bounds-array-to-pointer-decay

This check flags all array to pointer decays. Every single one. No cap. ๐Ÿ’…

Pointers should not be used as arrays, periodt. <cyan>span<T></cyan> is a bounds-checked, safe alternative to using pointers to access arrays โ€” it's giving responsible main character energy. โœจ

This rule is part of the Bounds safety (Bounds 3) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-bounds-decay> profile from the C++ Core Guidelines.

cppcoreguidelines-pro-bounds-avoid-unchecked-container-access

Catches calls to operator[] on STL containers and suggests swapping them for 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>. ๐ŸŒˆ

For example, both of these:

std::vector<int> a;
int b = a[4];

and this one:

std::unique_ptr<vector> a;
int b = a[0];

will generate a warning. Yeah bestie, the second one too. ๐Ÿซฃ

STL containers for which operator[] is well-defined for all inputs are excluded from this check (e.g.: std::map::operator[]). They're built different, they get a pass. ๐Ÿ‘‘

This check enforces part of the SL.con.3 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#slcon3-avoid-bounds-errors> guideline and is part of the Bounds Safety (Bounds 4) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-bounds-arrayindex> 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) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-bounds-arrayindex> profile from the C++ Core Guidelines.

Optionally, this check can generate fixes using gsl::at for indexing. Cute of it. ๐ŸŽ€

Options

  • GslHeader โ€” The check can generate fixes after this option has been set to the name of the include file that contains gsl::at(), e.g. "gsl/gsl.h". Default is an empty string.
  • IncludeStyle โ€” A string specifying which include-style is used, llvm or google. Default is llvm.

cppcoreguidelines-pro-bounds-pointer-arithmetic

This check flags all usage of pointer arithmetic, because it could lead to an invalid pointer. ๐Ÿ˜ฑ Subtraction of two pointers is not flagged by this check โ€” that one's off the hook.

Pointers should only refer to single objects, and pointer arithmetic is fragile and easy to get wrong. <cyan>span<T></cyan> is a bounds-checked, safe type for accessing arrays of data. It's the upgrade you deserve. ๐Ÿ’–

This rule is part of the Bounds safety (Bounds 1) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-bounds-arithmetic> 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 <#cmdoption-arg-StrictMode> option setting to determine whether it should flag all instances of const_cast or only those that remove either const or volatile qualifier. Choose your fighter. ๐ŸฅŠ

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. So likeโ€ฆ don't. ๐Ÿ™ˆ

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. Do not come for the volatile. ๐Ÿ’€

This rule is part of the Type safety (Type 3) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-constcast> profile and ES.50: Don't cast away const <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es50-dont-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. And no, bestie, that's not a vibe.

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) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-cstylecast> profile from the C++ Core Guidelines.

cppcoreguidelines-pro-type-member-init

The check flags user-provided constructor definitions that do not initialize all fields that would be left in an undefined state by default construction, e.g. builtins, pointers and record types without user-provided default constructors containing at least one such type. If these fields aren't initialized, the constructor will leave some of the memory in an undefined state. ๐Ÿ˜ญ Not the undefined memory, anything but that.

For C++11 it suggests fixes to add in-class field initializers. For older versions it inserts the field initializers into the constructor initializer list. It will also initialize any direct base classes that need to be zeroed in the constructor initializer list.

The check takes assignment of fields in the constructor body into account but generates false positives for fields initialized in methods invoked in the constructor body. Yeah, it's a little nosy sometimes. ๐Ÿซ 

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) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-memberinit> profile from the C++ Core Guidelines.

cppcoreguidelines-pro-type-reinterpret-cast

This check flags all uses of reinterpret_cast in C++ code. All of them. Every last one. ๐Ÿ‘€

Use of these casts can violate type safety and cause the program to access a variable that is actually of type X to be accessed as if it were of an unrelated type Z.

This rule is part of the Type safety (Type.1.1) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-reinterpretcast> 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. Glow-up included, no extra charge. โœจ

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) <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-downcast> profile from the C++ Core Guidelines.

โœจ Options โœจ

  • StrictMode โ€” set this to false and the check goes quiet: no warnings get emitted for casts on non-polymorphic types. Default is true.

cppcoreguidelines-pro-type-union-access

This one flags every single access to a member of a union. Passing a whole union around by value? Not flagged, bestie โ€” that part's chill.

Now the tea: 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. That's fragile because it cannot generally be enforced to be safe in the language, so it relies on programmer discipline to get it right. ๐Ÿซ 

This rule is part of the Type safety (Type.7) profile โ€” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-unions โ€” from the C++ Core Guidelines.


cppcoreguidelines-pro-type-vararg

Flags all calls to c-style vararg functions and all use of va_arg. Periodt.

But wait โ€” 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 if the function is used in an unevaluated context. So you get to keep your sneaky SFINAE tricks, no cap. ๐Ÿ’…

Why do we care? Because passing to varargs assumes the correct type will be read. That's fragile because it cannot generally be enforced to be safe in the language, so it relies on programmer discipline to get it right. ๐Ÿ˜ญ

This rule is part of the Type safety (Type.8) profile โ€” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#pro-type-varargs โ€” from the C++ Core Guidelines.


cppcoreguidelines-rvalue-reference-param-not-moved

Warns when an rvalue reference function parameter is never moved inside the function body.

Like, 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. It's giving "I forgot something" energy. ๐Ÿ™ˆ

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. Slay, the tool respects your [[maybe_unused]].

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);
}
  • 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 โ€” http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f18-for-will-move-from-parameters-pass-by-x-and-stdmove-the-parameter โ€” from the C++ Core Guidelines. ๐Ÿ‘‘


cppcoreguidelines-slicing

Flags slicing of member variables or vtable. ๐Ÿฐ (yes, slicing, but not the cake kind)

Slicing happens when you copy a derived object into a base object: the members of the derived object โ€” both member variables and virtual member functions โ€” will be discarded. This can be misleading, especially for member function slicing. Example:

struct B { int a; virtual int f(); };
struct D : B { int b; int f() override; };

void use(B b) {  // Missing reference, intended?
  b.f();  // Calls B::f.
}

D d;
use(d);  // Slice.

This check implements ES.63 โ€” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es63-dont-slice โ€” and C.145 โ€” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c145-access-polymorphic-objects-through-pointers-and-references โ€” from the C++ Core Guidelines.


cppcoreguidelines-special-member-functions

This check finds classes where some but not all of the special member functions are defined. It's giving incomplete friend group. ๐Ÿ˜ญ

By default the compiler defines a copy constructor, copy assignment operator, move constructor, move assignment operator and destructor. That default can be suppressed by explicit user-definitions. The relationship between which functions will be suppressed by definitions of other functions is complicated, and it is advised that all five are defaulted or explicitly defined. Just do all five, bestie, trust. ๐Ÿซถ

Note that defining a function with = delete is considered to be a definition.

This check implements C.21 โ€” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rc-five โ€” from the C++ Core Guidelines.

Options

  • AllowSoleDefaultDtor โ€” when set to true (default is false), this check will only trigger on destructors if they are defined and not defaulted.
struct A { // This is fine.
  virtual ~A() = default;
};

struct B { // This is not fine.
  ~B() {}
};

struct C {
  // This is not checked, because the destructor might be defaulted in
  // another translation unit.
  ~C();
};
  • AllowMissingMoveFunctions โ€” when set to true (default is false), this check doesn't flag classes which define no move operations at all. It still flags classes which define only one of either move constructor or move assignment operator. With this option enabled, the following class won't be flagged:
struct A {
  A(const A&);
  A& operator=(const A&);
  ~A();
};
  • AllowMissingMoveFunctionsWhenCopyIsDeleted โ€” when set to true (default is false), this check doesn't flag classes which define deleted copy operations but don't define move operations. This flag is related to Google C++ Style Guide Copyable and Movable Types โ€” https://google.github.io/styleguide/cppguide.html#Copyable_Movable_Types. With this option enabled, the following class won't be flagged:
struct A {
  A(const A&) = delete;
  A& operator=(const A&) = delete;
  ~A();
};
  • AllowImplicitlyDeletedCopyOrMove โ€” when set to true (default is false), this check doesn't flag classes which implicitly delete copy or move operations. With this option enabled, the following class won't be flagged:
struct A : boost::noncopyable {
  ~A() { std::cout << "dtor\n"; }
};
  • IgnoreMacros โ€” if set to true, the check will not give warnings for classes defined inside macros. Default is true.

cppcoreguidelines-use-default-member-init

This check implements C.48 โ€” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rc-in-class-initializer โ€” from the C++ Core Guidelines.

The cppcoreguidelines-use-default-member-init check is an alias, bestie, so please go see modernize-use-default-member-init for more information. ๐Ÿ’…


cppcoreguidelines-use-enum-class

Finds unscoped (non-class) enum declarations and suggests using enum class instead. Glow-up for your enums, fr fr. โœจ

This check implements Enum.3 โ€” https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#renum-class โ€” 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.

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

This check implements C.35 โ€” http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rc-dtor-virtual โ€” 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. It does not care about your intentions, bestie. ๐Ÿ’€

Fixes are available for user-declared and implicit destructors that are either public and non-virtual or protected and virtual. No fixes are offered for private destructors. There, the decision whether to make them private and virtual or protected and non-virtual depends on the use case and is thus left to the user. That one's on you. ๐Ÿซก

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, honestly. ๐Ÿ˜ฌ

This check will detect the following function invocations:

  • OSSpinlockLock
  • OSSpinlockTry
  • OSSpinlockUnlock

The corresponding info about the whole OSSpinlock situation lives right here: <https://blog.postmates.com/why-spinlocks-are-bad-on-ios-b69fc5221058>


๐Ÿฆ‹ darwin-dispatch-once-nonstatic

Okay bestie, this one catches dispatch_once_t variables that get declared without static or global storage. Like, why does that matter? Because libdispatch literally says using dispatch_once_t predicates with automatic or dynamic storage is undefined behavior, and we do not do undefined behavior in this house. Avoid it, periodt. ๐Ÿ’€

So the classic move is: you got a function that wants to initialize some internal static or global data exactly once when it runs. Super normal, super fine. But people keep forgetting the static on the dispatch_once_t predicate, which means the flag value is just sitting there uninitialized, at the mercy of the stack. ๐Ÿ™ˆ And the stack does not care about your feelings.

People have also been out here making dispatch_once_t variables be members of structs or classes, thinking they're gonna lazily do some expensive struct-or-class-member init just one time. Cute idea! But no โ€” that violates the libdispatch requirements. ๐Ÿ˜ญ

For more deets, go read the discussion section of Apple's dispatch_once documentation: <https://developer.apple.com/documentation/dispatch/1447169-dispatch_once>


๐ŸŒธ fuchsia-default-arguments-calls

Warns you if a function or method gets called with default arguments. Yeah, called, not declared โ€” different check, keep reading. ๐Ÿ‘‘

For example, given this declaration:

int foo(int value = 5) { return value; }

Any function call expression that uses a default argument gets diagnosed. But calling it without defaults? No warning, you're fine:

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

Now this one warns if a function or method is declared with default parameters.

For example, this declaration:

int foo(int value = 5) { return value; }

will cause a warning. That's it, that's the check. ๐Ÿ’…

See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>


๐ŸŽ€ fuchsia-header-anon-namespaces

Plot twist: the fuchsia-header-anon-namespaces check is just an alias. Please see misc-anonymous-namespace-in-header <> for more info. iykyk โœจ


๐Ÿ’œ fuchsia-multiple-inheritance

The fuchsia-multiple-inheritance check is also an alias, so please see misc-multiple-inheritance <> for the 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 operators (copy and move). Those get a pass.

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 get constructed โ€” unless the object is statically initialized with a constexpr constructor or has no explicit constructor. Two escape hatches, that's it.

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 an object should be flagged, you gotta explicitly pass its fully qualified type name to the check. No name, no flag.

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 that same list, these 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
Heads up bestie: objects must be explicitly specified in order to be flagged, so objects that inherit a specified object will not be flagged. ๐Ÿšจ

This check matches temporary objects without regard for inheritance โ€” meaning 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 the empty string.

๐Ÿงš fuchsia-trailing-return

Functions with trailing returns are disallowed โ€” except for the ones using decltype specifiers and lambdas with otherwise unutterable return types. Those get a hall pass.

For example:

// No warning
int add_one(const int arg) { return arg; }

// Warning
auto get_add_one() -> int (*)(const int) {
  return add_one;
}

Exceptions are made for lambdas and decltype specifiers:

// No warning
auto lambda = [](double x, double y) -> double {return x + y;};

// No warning
template <typename T1, typename T2>
auto fn(const T1 &lhs, const T2 &rhs) -> decltype(lhs + rhs) {
  return lhs + rhs;
}

See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>


๐Ÿ“ fuchsia-virtual-inheritance

Warns if classes are defined with virtual inheritance. And like, they shouldn't be.

For example, classes should not be defined with virtual inheritance:

class B : public virtual A {};   // warning

See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en>


๐Ÿ‘‘ google-build-explicit-make-pair

Check that make_pair's template arguments are deduced. Like, let the compiler cook. ๐Ÿง‘โ€๐Ÿณ

Real talk: G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are specified explicitly, and such use isn't intended in any case.

Corresponding cpplint.py check name: build/explicit_make_pair.


๐Ÿซถ google-build-namespaces

The google-build-namespaces check is an alias, please see misc-anonymous-namespace-in-header <> for more info.

Finds anonymous namespaces in headers. Yeah bestie, don't do that in a header. ๐Ÿ™ˆ

<https://google.github.io/styleguide/cppguide.html#Namespaces>

Corresponding cpplint.py check name: build/namespaces.


๐Ÿ’… google-build-using-namespace

Finds using namespace directives. The check implements this rule from the Google C++ Style Guide <https://google.github.io/styleguide/cppguide.html#Namespaces>:

You may not use a using-directive to make all names from a namespace available.
// Forbidden -- This pollutes the namespace.
using namespace foo;

Corresponding cpplint.py check name: build/namespaces.


๐ŸŽ€ google-default-arguments

Checks that default arguments are not given for virtual methods. That's the whole vibe.

See <https://google.github.io/styleguide/cppguide.html#Default_Arguments>


โœจ google-explicit-constructor

Checks that constructors callable with a single argument โ€” and conversion operators โ€” are marked explicit, so we avoid the risk of unintentional implicit conversions. It's giving safety. ๐Ÿ”ฅ

Consider this example:

struct S {
  int x;
  operator bool() const { return true; }
};

bool f() {
  S a{1};
  S b{2};
  return a == b;
}

The function will return true, since the objects are implicitly converted to bool before comparison, which is unlikely to be the intent. ๐Ÿ’€ Yeah. Not what you meant.

The check will suggest inserting explicit before the constructor or conversion operator declaration. However, copy and move constructors should not be explicit, and neither should constructors taking a single initializer_list argument.

So this code:

struct S {
  S(int a);
  explicit S(const S&);
  operator bool() const;
  ...

will become:

struct S {
  explicit S(int a);
  S(const S&);
  explicit operator bool() const;
  ...

See <https://google.github.io/styleguide/cppguide.html#Explicit_Constructors>


๐ŸŒˆ google-global-names-in-headers

Flags global namespace pollution in header files. Right now it only triggers on using declarations and directives. Small but mighty. ๐Ÿ’ช

The relevant style guide section is <https://google.github.io/styleguide/cppguide.html#Namespaces>.


๐Ÿฆ‹ google-objc-avoid-nsobject-new

Finds calls to +new or overrides of it โ€” both are prohibited by the Google Objective-C style guide.

The Google Objective-C style guide forbids calling +new or overriding it in class implementations, preferring +alloc and -init methods to instantiate objects.

An example:

NSDate *now = [NSDate new];
Foo *bar = [Foo new];

Instead, code should use +alloc/-init or class factory methods:

NSDate *now = [NSDate date];
Foo *bar = [[Foo alloc] init];

This check corresponds to the Google Objective-C Style Guide rule Do Not Use +new <https://google.github.io/styleguide/objcguide.html#do-not-use-new>.


๐Ÿ’œ google-objc-avoid-throwing-exception

Finds uses of throwing exceptions in Objective-C files. For the same reason as the Google C++ style guide, we prefer not throwing exceptions from Objective-C code.

The corresponding C++ style guide rule: <https://google.github.io/styleguide/cppguide.html#Exceptions>

Instead, prefer passing in NSError ** and return BOOL to indicate success or failure. ๐Ÿ‘‘

A counterexample:

- (void)readFile {
  if ([self isError]) {
    @throw [NSException exceptionWithName:...];
  }
}

Instead, returning an error via NSError ** is preferred:

- (BOOL)readFileWithError:(NSError **)error {
  if ([self isError]) {
    *error = [NSError errorWithDomain:...];
    return NO;
  }
  return YES;
}

The corresponding style guide rule: <https://google.github.io/styleguide/objcguide.html#avoid-throwing-exceptions>


๐ŸŒธ google-objc-function-naming

Finds function declarations in Objective-C files that don't follow the pattern described in the Google Objective-C Style Guide. Naming matters, bestie โ€” it's the main character energy of your codebase. โœจ

The corresponding style guide rule can be found here: <https://google.github.io/styleguide/objcguide.html#function-names>

All function names should be in Pascal case, no exceptions. And functions whose storage class isn't static? They need an appropriate prefix too, bestie. ๐Ÿ’…

Here's code that is not doing the assignment:

static bool is_positive(int i) { return i > 0; }
bool IsNegative(int i) { return i < 0; }

and the glow-up version of that same sample is:

static bool IsPositive(int i) { return i > 0; }
bool *ABCIsNegative(int i) { return i < 0; }

google-objc-global-variable-declaration</p>

Catches global variable declarations in Objective-C files that aren't matching the variable-name pattern from Google's Objective-C Style Guide. ๐ŸŽ€

The style guide rule it's serving: https://google.github.io/styleguide/objcguide.html#variable-names

Every global variable needs to follow the pattern g[A-Z].* (that's for variables) or k[A-Z].* (that's for constants). If the check can figure out what your name should be from the original name, it'll go ahead and suggest it. ๐Ÿซถ

So for code like:

static NSString* myString = @"hello";

the fix it hands you is:

static NSString* gMyString = @"hello";

Another one, a constant this time:

static NSString* const myConstString = @"hello";

and the fix:

static NSString* const kMyConstString = @"hello";

But โ€” and this is the part you gotta clock โ€” if the code is prefixed with non-alphabetical characters, like:

static NSString* __anotherString = @"world";

the check will give you a warning message but it literally cannot suggest a fix. You're on your own for that one, bestie. Fix it yourself. ๐Ÿ˜ญ


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

Heads up: the FRIEND_TEST macro is not included. ๐Ÿšซ

For example:

TEST(TestSuiteName, Illegal_TestName) {}
TEST(Illegal_TestSuiteName, TestName) {}

would trigger the check, periodt. Underscores are not allowed in test suite names or test names โ€” https://google.github.io/googletest/faq.html#why-should-test-suite-names-and-test-names-not-contain-underscore.

The DISABLED_ prefix (you know, the one you use to disable test suites and individual tests โ€” https://google.github.io/googletest/advanced.html#temporarily-disabling-tests) gets stripped from the test suite name and test name before the check goes looking for underscores. ๐Ÿงš

This check does not propose any fixes. Nope. Not one.


google-readability-braces-around-statements

The google-readability-braces-around-statements check is an alias โ€” please see readability-braces-around-statements <> for more information. โœจ


google-readability-casting

The google-readability-casting check 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

The google-readability-function-size check is an alias โ€” please see readability-function-size <> for more information. ๐Ÿ’›


google-readability-namespace-comments

The google-readability-namespace-comments check is an alias โ€” please see llvm-namespace-comment <> for more information. ๐ŸŒธ


google-readability-todo

Finds TODO comments that are missing a username or a bug number. It's giving "who even left this note". ๐Ÿ™ˆ

The relevant style guide section: 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 using them, because of the lack of portability. ๐Ÿ‘‘

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 the equivalent APIs containing suite. โœจ

Every name containing case is being replaced so things stay consistent with the meanings of "test case" and "test suite" as used by the International Software Testing Qualifications Board and ISO 29119.

The new names are a part of Google Test version 1.9 (release pending). It is recommended that users update their dependency to version 1.9 and then use this check to remove the 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 provide only a warning when a fix can't be made safely โ€” like with some template and macro uses. ๐Ÿ”ฅ


hicpp-avoid-c-arrays

The hicpp-avoid-c-arrays check is an alias โ€” please see modernize-avoid-c-arrays <> for more information. It partly enforces the rule 4.1.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-conversions. ๐Ÿ’œ


hicpp-avoid-goto

The hicpp-avoid-goto check is an alias โ€” please see cppcoreguidelines-avoid-goto <> for more information. It enforces the rule 6.3.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements.


hicpp-braces-around-statements

The hicpp-braces-around-statements check is an alias โ€” please see readability-braces-around-statements <> for more information. It enforces the rule 6.1.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements. ๐ŸŽ€


hicpp-deprecated-headers

The hicpp-deprecated-headers check is an alias โ€” please see modernize-deprecated-headers <> for more information. It enforces the rule 1.3.3 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/general. ๐ŸŒˆ


hicpp-exception-baseclass

Make sure that every value in a throw expression is an instance of std::exception. ๐Ÿ˜ค

This enforces rule 15.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-exception-handling โ€” of the High Integrity C++ Coding Standard.

class custom_exception {};

void throwing() noexcept(false) {
  // Problematic throw expressions.
  throw int(42);
  throw custom_exception();
}

class mathematical_error : public std::exception {};

void throwing2() noexcept(false) {
  // These kind of throws are ok.
  throw mathematical_error();
  throw std::runtime_error();
  throw std::exception();
}

hicpp-explicit-conversions

This check is an alias for google-explicit-constructor <>. ๐Ÿ’…

Used to enforce parts of rule 5.4.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-expressions. This check will enforce that constructors and conversion operators are marked explicit. Other forms of casting checks live in other places. The following checks can be used to check for more forms of casting:

  • cppcoreguidelines-pro-type-static-cast-downcast <>
  • cppcoreguidelines-pro-type-reinterpret-cast <>
  • cppcoreguidelines-pro-type-const-cast <>
  • cppcoreguidelines-pro-type-cstyle-cast <>

hicpp-function-size

This check is an alias for readability-function-size <>. Useful to enforce multiple sections on function complexity. ๐Ÿ‘‘


hicpp-ignored-remove-result

Make sure the result of std::remove, std::remove_if and std::unique are not ignored, according to rule 17.5.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-library. ๐Ÿšจ

The mutating algorithms std::remove, std::remove_if and both overloads of std::unique operate by swapping or moving elements of the range they're operating over. On completion, they return an iterator to the last valid element. In the majority of cases the correct behavior is to use this result as the first operand in a call to std::erase.

This check is a subset of bugprone-unused-return-value <> and, depending on used options, it can be superfluous to enable both checks. ๐Ÿ’€

Options

  • AllowCastToVoid โ€” controls whether casting return values to void is permitted. Default: true.

hicpp-invalid-access-moved

This check is an alias for bugprone-use-after-move <>. ๐Ÿฆ‹

It implements parts of the rule 8.4.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/definitions โ€” to check if moved-from objects are accessed.


hicpp-member-init

This check is an alias for cppcoreguidelines-pro-type-member-init <>. It implements the check for rule 12.4.2 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions โ€” to initialize class members in the right order. โœจ


hicpp-move-const-arg

The hicpp-move-const-arg check is an alias โ€” please see performance-move-const-arg <> for more information. It enforces the rule 17.3.1 โ€” https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-library. ๐Ÿ’œ

hicpp-multiway-paths-covered โœจ

Bestie, this check is out here catching situations where your code paths are not fully covered. It also gently suggests you use if instead of switch when if would just be clearer. It enforces rule 6.1.2 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements) and rule 6.1.4 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements) of the High Integrity C++ Coding Standard. Yes, both links are the same, that's just how it is, don't come for me ๐Ÿ’…

So like โ€” if-else if chains that never land on a final else branch? Those can send your program somewhere unhinged and are usually the result of a logic oopsie. If you genuinely meant to skip the else, you can leave it empty with a clarifying comment. Heads up though: this warning gets noisy on some code bases, so it's disabled by default. ๐Ÿ™ˆ

void f1() {
  int i = determineTheNumber();

   if(i > 0) {
     // Some Calculation
   } else if (i < 0) {
     // Precondition violated or something else.
   }
   // ...
}

Same energy for switch statements that don't cover all possible code paths, fr fr:

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

Rule 6.1.4 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/statements) says every switch needs at least two case labels besides a default label. Otherwise? The switch would honestly just be better as an if. Degenerated switch statements with zero labels get caught too. ๐Ÿ‘‘

// Degenerated switch that could be better written as `if`
int i = 42;
switch(i) {
  case 1: // do something here
  default: // do something else here
}

// Should rather be the following:
if (i == 1) {
  // do something here
}
else {
  // do something here
}

// A completely degenerated switch will be diagnosed.
int i = 42;
switch(i) {}

Options ๐ŸŽ€

  • WarnOnMissingElse โ€” Boolean flag that activates a warning for missing else branches. Default is false.

hicpp-named-parameter ๐ŸŒธ

This check is an alias for readability-named-parameter. It implements rule 8.2.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/definitions).


hicpp-new-delete-operators ๐Ÿ’–

This check is an alias for misc-new-delete-overloads. It implements rule 12.3.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions) to make sure the new and delete operators have the correct signature.


hicpp-no-array-decay ๐Ÿฆ‹

The hicpp-no-array-decay check is an alias, please see cppcoreguidelines-pro-bounds-array-to-pointer-decay for more information. It enforces rule 4.1.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-conversions).


hicpp-no-assembler ๐ŸŽ€

Checks for assembler statements. Use of inline assembly should be avoided since it restricts the portability of the code. (Yes bestie, portability matters, we love a codebase that runs everywhere ๐Ÿ˜ญ)

This enforces rule 7.5.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/declarations) of the High Integrity C++ Coding Standard.


hicpp-no-malloc โœจ

The hicpp-no-malloc check is an alias, please see cppcoreguidelines-no-malloc for more information. It enforces rule 5.3.2 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-expressions).


hicpp-noexcept-move ๐Ÿ’œ

This check is an alias for performance-noexcept-move-constructor. It checks rule 12.5.4 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions) to mark move assignment and move construction noexcept.


hicpp-signed-bitwise ๐Ÿ”ฅ

Finds uses of bitwise operations on signed integer types, which may lead to undefined or implementation defined behavior. (Not the cute kind of surprise. The bad kind. ๐Ÿ’€)

The according rule is defined in the High Integrity C++ Standard, Section 5.6.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard-expressions).

Options ๐ŸŽ€

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

hicpp-special-member-functions ๐Ÿ‘‘

This check is an alias for cppcoreguidelines-special-member-functions. It checks that special member functions have the correct signature, according to rule 12.5.7 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions).


hicpp-static-assert ๐ŸŒˆ

The hicpp-static-assert check is an alias, please see misc-static-assert for more information. It enforces the rule 7.1.10 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/declarations).


hicpp-undelegated-constructor ๐Ÿซถ

This check is an alias for bugprone-undelegated-constructor. It partially implements rule 12.4.5 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions) to find misplaced constructor calls inside a constructor.

struct Ctor {
  Ctor();
  Ctor(int);
  Ctor(int, int);
  Ctor(Ctor *i) {
    // All Ctor() calls result in a temporary object
    Ctor(); // did you intend to call a delegated constructor?
    Ctor(0); // did you intend to call a delegated constructor?
    Ctor(1, 2); // did you intend to call a delegated constructor?
    foo();
  }
};

hicpp-uppercase-literal-suffix โญ

The hicpp-uppercase-literal-suffix check is an alias, please see readability-uppercase-literal-suffix for more information.

Partially implements rule 4.2.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-conversions) to ensure that the U suffix is writeln properly. (That's what the original says, I'm not touching it, take it up with the standard ๐Ÿ’…)


hicpp-use-auto ๐Ÿ’…

The hicpp-use-auto check is an alias, please see modernize-use-auto for more information. It enforces the rule 7.1.8 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/declarations).


hicpp-use-emplace ๐Ÿ“

The hicpp-use-emplace check is an alias, please see modernize-use-emplace for more information. It enforces the rule 17.4.2 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/standard-library).


hicpp-use-equals-default ๐ŸŽ€

This check is an alias for modernize-use-equals-default. It implements rule 12.5.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions) to explicitly default special member functions.


hicpp-use-equals-delete ๐Ÿงš

This check is an alias for modernize-use-equals-delete. It implements rule 12.5.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/special-member-functions) to explicitly default or delete special member functions.


hicpp-use-noexcept โœจ

The hicpp-use-noexcept check is an alias, please see modernize-use-noexcept for more information. It enforces the rule 1.3.5 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/general).


hicpp-use-nullptr ๐ŸŒธ

The hicpp-use-nullptr check is an alias, please see modernize-use-nullptr for more information. It enforces the rule 2.5.3 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/lexical-conventions).


hicpp-use-override ๐Ÿ‘‘

This check is an alias for modernize-use-override. It implements rule 10.2.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/derived-classes) to declare a virtual function override when overriding.


hicpp-vararg ๐Ÿ’–

The hicpp-vararg check is an alias, please see cppcoreguidelines-pro-type-vararg for more information. It enforces the rule 14.1.1 (https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standard/templates).


linuxkernel-must-check-errs ๐Ÿง

Checks Linux kernel code to see if it uses the results from the functions in linux/err.h. Also checks to see if 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 ERR_PTR, PTR_ERR, IS_ERR, IS_ERR_OR_NULL, ERR_CAST, and PTR_ERR_OR_ZERO return values must be checked, since positive pointers and negative error codes are being used in the same context. These functions are marked with __attribute__((warn_unused_result)), but some kernel versions do not have this warning enabled for clang. (So like, don't rely on the compiler catching it for you, bestie ๐Ÿ˜ญ)

Examples:

/* Trivial unused call to an ERR function */
PTR_ERR_OR_ZERO(some_function_call());

/* A function that returns ERR_PTR. */
void *fn() { ERR_PTR(-EINVAL); }

/* An invalid use of fn. */
fn();

llvm-else-after-return ๐ŸŒˆ

The llvm-else-after-return check is an alias, please see readability-else-after-return for more information.


llvm-header-guard ๐Ÿ›ก๏ธ

Finds and fixes header guards that do not adhere to LLVM style.


llvm-include-order ๐Ÿ’œ

Checks the correct order of #includes.

See https://llvm.org/docs/CodingStandards.html#include-style


llvm-namespace-comment ๐ŸŒŸ

google-readability-namespace-comments redirects here as an alias for this check.

Checks that long namespaces have a closing comment.

https://llvm.org/docs/CodingStandards.html#namespace-indentation

https://google.github.io/styleguide/cppguide.html#Namespaces

namespace n1 {
void f();
}

// becomes

namespace n1 {
void f();
}  // namespace n1

Options ๐ŸŽ€

  • ShortNamespaceLines โ€” 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. The value is an unsigned integer that defaults to 1U.
  • SpacesBeforeComments โ€” An unsigned integer specifying the number of spaces before the comment closing a namespace definition. Default is 1U.
  • AllowOmittingNamespaceComments โ€” When true, the check will accept if no namespace comment is present. The check will only fail if the specified namespace comment is different than expected. Default is false.

๐Ÿ” llvm-prefer-isa-or-dyn-cast-in-conditionals

okay bestie this one is SO satisfying, it just goes snooping through your conditionals and finds those little cast<> and dyn_cast<> moments that are lowkey wrong. Here's the tea:

  • cast<> in a conditional will assert instead of just handing you back a null pointer. Rude.
  • dyn_cast<> where you never actually capture the return value? Also pointless.
  • And it catches the pattern var && isa<X>(var), where var gets evaluated twice. Not the vibe.
// Finds these:
if (auto x = cast<X>(y)) {}
// is replaced by:
if (auto x = dyn_cast<X>(y)) {}

if (cast<X>(y)) {}
// is replaced by:
if (isa<X>(y)) {}

if (dyn_cast<X>(y)) {}
// is replaced by:
if (isa<X>(y)) {}

if (var && isa<T>(var)) {}
// is replaced by:
if (isa_and_nonnull<T>(var.foo())) {}

// Other cases are ignored, e.g.:
if (auto f = cast<Z>(y)->foo()) {}
if (cast<Z>(y)->foo()) {}
if (X.cast(y)) {}

๐Ÿ“ llvm-prefer-register-over-unsigned

this one is a whole archaeology dig ๐Ÿบ it finds historical use of unsigned to hold vregs and physregs and rewrites them to use Register. periodt.

Right now it works by finding every variable of unsigned integer type whose initializer starts with an implicit cast from Register to unsigned. Sneaky little thing.

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 chillin' inside an anonymous namespace and is like "bestieโ€ฆ static? ๐Ÿฅบ"

The LLVM Coding Standards (linked at https://llvm.org/docs/CodingStandards.html#restrict-visibility) 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.

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 anonymous namespace outside of the corresponding class will be warned. Default value is true.

โœจ llvm-qualified-auto

The llvm-qualified-auto check is an alias, so go peek at readability-qualified-auto for the actual details. ๐Ÿ’…


๐Ÿชข llvm-twine-local

looks for local Twine variables, which are super prone to use-after-frees and should generally be avoided. it's giving dangling pointer energy ๐Ÿ’€

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 Twine created from temporary strings or string literals. No cap.

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
}

After applying this fix-it hints, the code will use std::string instead of Twine for local variables. However, Twine has lots of methods that are incompatible with std::string, so the user may need to adjust the code manually after applying the fix-it hints. fair warning 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. glow-up incoming โœจ

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. it's the range-based era, babe.

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::all_of, std::any_of, std::binary_search, std::copy, std::copy_if, std::count, std::count_if, std::equal, std::fill, std::find, std::find_if, std::find_if_not, std::for_each, std::includes, std::is_sorted, std::lower_bound, std::max_element, std::min_element, std::mismatch, std::none_of, std::partition, std::partition_point, std::remove_if, std::replace, std::stable_sort, std::transform, std::uninitialized_copy, std::unique, std::upper_bound.

The check will add the necessary #include "llvm/ADT/STLExtras.h" directive when applying fixes. ๐Ÿซถ


๐Ÿ›๏ธ llvmlibc-callee-namespace

checks all calls resolve to functions within the correct namespace. no strays allowed.

// Implementation inside the LIBC_NAMESPACE namespace.
// Correct if:
// - LIBC_NAMESPACE is a macro
// - LIBC_NAMESPACE expansion starts with `__llvm_libc`
namespace LIBC_NAMESPACE {

// Allow calls with the fully qualified name.
LIBC_NAMESPACE::strlen("hello");

// Allow calls to compiler provided functions.
(void)__builtin_abs(-1);

// Bare calls are allowed as long as they resolve to the correct namespace.
strlen("world");

// Disallow calling into functions in the global namespace.
::strlen("!");

} // namespace LIBC_NAMESPACE

๐Ÿงบ llvmlibc-implementation-in-namespace

checks that all declarations in the llvm-libc implementation are within the correct namespace. keep it contained, bestie.

// Implementation inside the LIBC_NAMESPACE_DECL namespace.
// Correct if:
// - LIBC_NAMESPACE_DECL is a macro
// - LIBC_NAMESPACE_DECL expansion starts with `[[gnu::visibility("hidden")]] __llvm_libc`
namespace LIBC_NAMESPACE_DECL {
    void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
    // Namespaces within LIBC_NAMESPACE_DECL namespace are allowed.
    namespace inner {
        int localVar = 0;
    }
    // Functions with C linkage are allowed.
    extern "C" void str_fuzz() {}
}

// Incorrect: implementation not in the LIBC_NAMESPACE_DECL namespace.
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}

// Incorrect: outer most namespace is not the LIBC_NAMESPACE_DECL macro.
namespace something_else {
    void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
}

// Incorrect: outer most namespace expansion does not start with `[[gnu::visibility("hidden")]] __llvm_libc`.
#define LIBC_NAMESPACE_DECL custom_namespace
namespace LIBC_NAMESPACE_DECL {
    void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
}

๐Ÿ’ซ llvmlibc-inline-function-decl

checks that all implicitly and explicitly inline functions in header files are tagged with the LIBC_INLINE macro โ€” except for functions implicit to classes or deleted functions. See the libc style guide (https://libc.llvm.org/dev/code_style.html) for more info about this macro. tag it or it didn't happen ๐Ÿท๏ธ


๐Ÿšซ llvmlibc-restrict-system-libc-headers

finds includes of system libc headers not provided by the compiler, hiding out inside llvm-libc implementations.

#include <stdio.h>            // Not allowed because it is part of system libc.
#include <stddef.h>           // Allowed because it is provided by the compiler.
#include "internal/stdio.h"   // Allowed because it is NOT part of system libc.

This check is necessary because accidentally including system libc headers can lead to subtle and hard to detect bugs. For example, consider a system libc whose dirent struct has slightly different field ordering than llvm-libc. While this will compile successfully, this can cause issues during runtime because they are ABI incompatible. likeโ€ฆ it compiles but at what cost ๐Ÿ˜ญ

โš™๏ธ 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. nope nope nope.

Anonymous namespaces in headers can lead to One Definition Rule (ODR) violations, because each translation unit including the header will get its own unique version of the symbols. This increases binary size and can cause confusing link-time errors. chaos fr fr.

๐Ÿ“š References

This check corresponds to the CERT C++ Coding Standard rule DCL59-CPP. Do not define an unnamed namespace in a header file (https://wiki.sei.cmu.edu/confluence/display/cplusplus/DCL59-CPP.+Do+not+define+an+unnamed+namespace+in+a+header+file).

Corresponding cpplint.py check name: build/namespaces.


๐Ÿ‘ฏ misc-confusable-identifiers

warns about confusable identifiers โ€” identifiers that look visually close to each other but use different Unicode characters. This detects a potential attack described in CVE-2021-42574 (https://www.cve.org/CVERecord?id=CVE-2021-42574). scary but iconic that we caught it ๐Ÿ•ต๏ธ

Example:

int fo; // Initial character is U+0066 (LATIN SMALL LETTER F).
int ๐Ÿo; // Initial character is U+1D41F (MATHEMATICAL BOLD SMALL F) not U+0066 (LATIN SMALL LETTER F).

๐Ÿ”’ misc-const-correctness

This check implements detection of local variables which could be declared as const but are not. Declaring variables as const is required or recommended by many coding guidelines, such as: ES.25 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es25-declare-an-object-const-or-constexpr-unless-you-want-to-modify-its-value-later-on) from the C++ Core Guidelines.

Please note that this check's analysis is type-based only ๐Ÿ’… like, it only cares about types, okay bestie? So if you've got variables that never get modified but they're being used to build a non-const handle that might escape the scope? Nah, that's not getting diagnosed as potential const. Sorry not sorry. ๐Ÿ™ˆ

// 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 โ€” it's giving range, honestly ๐Ÿ‘‘

// Normal values like built-ins or objects.
int potential_const_int = 42;       // Before transformation
int const potential_const_int = 42; // After transformation
int copy_of_value = potential_const_int;

MyClass could_be_const;       // Before transformation
MyClass const could_be_const; // After transformation
could_be_const.const_qualified_method();

// References can be declared const as well.
int &reference_value = potential_const_int;       // Before transformation
int const& reference_value = potential_const_int; // After transformation
int another_copy = reference_value;

// The similar semantics of pointers are analyzed.
int *pointer_variable = &potential_const_int; // Before transformation
int const*const pointer_variable = &potential_const_int; // After transformation, both pointer itself and pointee are supported.
int last_copy = *pointer_variable;

The automatic code transformation is only applied to variables that are declared in single declarations. So you might wanna prep your codebase with readability-isolate-declaration <> first, bestie ๐ŸŽ€

Also lowkey there's this other check, cppcoreguidelines-avoid-non-const-global-variables <>, that enforces const correctness on all globals. Just so you know. โœจ


๐Ÿ’€ Limitations ๐Ÿ’€

The check does not run on C code. Periodt.

The check will not analyze templated variables, or variables that are instantiation dependent. Because different instantiations can give you different const correctness properties, and ngl it's just not possible to find all instantiations of a template. The template might be used totally differently in an independent translation unit. ๐Ÿ˜ญ


โœจ Options โœจ

  • AnalyzeValues โ€” turn the analysis of ordinary value variables on or off, 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 โ€” turn the analysis of reference variables on or off, like int &ref = i;. Default is true.
int i = 42;
// Warning
int& ref = i;
// No warning
int const& ref = i;
  • AnalyzePointers โ€” turn the analysis of pointers variables on or off, like int *ptr = &i;. For specific checks, see WarnPointersAsValues and WarnPointersAsPointers. Default is true.
  • 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.

int value = 42;

// No warning
const int *const pointer_variable = &value;
// Warning
int *const pointer_variable = &value;
  • TransformValues โ€” provides fixit-hints for value types that automatically add const if 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 โ€” provides fixit-hints for reference types that automatically add const if 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 โ€” provides fixit-hints for pointers if their pointee is not changed. This does not analyze if the value-pointed-to is unchanged! Default is false.

Requires 'WarnPointersAsValues' to be 'true'.

int value = 42;

// Before
const int * pointer_variable = &value;
// After
const int *const pointer_variable = &value;

// Before
const int * a[] = {&value, &value};
// After
const int *const a[] = {&value, &value};

// Before
int *ptr_value = &value;
// After
int *const ptr_value = &value;

int result = 100 * (*ptr_value); // Does not modify the pointer itself.
// This modification of the pointee is still allowed and not diagnosed.
*ptr_value = 0;

// The following pointer may not become a 'int *const'.
int *changing_pointee = &value;
changing_pointee = &result;
  • TransformPointersAsPointers โ€” provides fix-it hints for pointers if the value it pointing to is not changed. Default is false.

Requires WarnPointersAsPointers to be true.

int value = 42;

// Before
int * pointer_variable = &value;
// After
const int * pointer_variable = &value;

// Before
int * a[] = {&value, &value};
// After
const int * a[] = {&value, &value};
  • AllowedTypes โ€” a semicolon-separated list of names of types that will be excluded from const-correctness checking. Regular expressions are accepted, e.g. [Rr]ef(erence)?$ matches every type with 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 empty string.

๐Ÿฆ‹ misc-coroutine-hostile-raii ๐Ÿฆ‹

Detects when objects of certain hostile RAII types persist across suspension points in a coroutine. Such hostile types include scoped-lockable types and types belonging to a configurable denylist. ๐Ÿ’€

Some objects require that they be destroyed on the same thread that created them. Traditionally this requirement was often phrased as "must be a local variable", under the assumption that local variables always work this way. But nope, that's incorrect with C++20 coroutines, bestie, because an intervening co_await may cause the coroutine to suspend and later be resumed on another thread. ๐Ÿ˜ฑ

The lifetime of an object that requires being destroyed on the same thread must not encompass a co_await or co_yield point. If you create/destroy an object, you must do so without allowing the coroutine to suspend in the meantime. Periodt. ๐Ÿ‘‘

Following types are considered as hostile:

  • Scoped-lockable types: a scoped-lockable object persisting across a suspension point is problematic, because the lock held by this object could be unlocked by a different thread. This would be undefined behaviour. ๐Ÿ˜ญ This includes all types annotated with the scoped_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 which should not be allowed to persist across suspension points. Eg: my::lockable;a::b;::my::other::lockable The default value of this option is std::lock_guard;std::scoped_lock.
  • AllowedAwaitablesList โ€” a semicolon-separated list of qualified types of awaitables types which can be safely awaited while having hostile RAII objects in scope. ๐Ÿซถ

co_await-ing an expression of awaitable type is considered safe if the awaitable type is part of this list. RAII objects persisting across such a co_await expression are considered safe and hence are not flagged.

Example usage:

// Consider option AllowedAwaitablesList = "safe_awaitable"
struct safe_awaitable {
  bool await_ready() noexcept { return false; }
  void await_suspend(std::coroutine_handle<>) noexcept {}
  void await_resume() noexcept {}
};
auto wait() { return safe_awaitable{}; }

task coro() {
  // This persists across both the co_await's but is not flagged
  // because the awaitable is considered safe to await on.
  const std::lock_guard l(&mu_);
  co_await safe_awaitable{};
  co_await wait();
}

Eg: my::safe::awaitable;other::awaitable Default is an empty string.

  • AllowedCallees โ€” a semicolon-separated list of callee function names which can be safely awaited while having hostile RAII objects 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 โ€” and bestie, the default is just an empty string. ๐Ÿ’…


misc-definitions-in-headers ๐ŸŽ€

Okay so this one hunts down non-extern non-inline function and variable definitions just chilling in header files. And likeโ€ฆ that's a problem, because if that header gets included from multiple translation units? Boom ๐Ÿ’€ โ€” potential ODR violations. It's giving chaos.

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

And when clang-tidy gets invoked with the --fix-notes option, this check hands out fixes that auto-add the inline keyword to the functions it caught. โœจ But heads up bestie: adding inline to variables is not currently supported by this check. No cap. ๐Ÿซถ


misc-header-include-cycle ๐Ÿ”„

This check detects cyclic #include dependencies between user-defined headers. Round and round we go ๐ŸŽ 

// 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

So like, header files are a crucial part of so many C++ programs, right? They give you a way to organize declarations and definitions that get shared across multiple source files. But ๐Ÿ’€ header files can also create problems when they get all entangled in complex dependency cycles. And those cycles? They cause issues with compilation times, unnecessary rebuilds, and they make it way harder to understand the overall structure of the code.

To address all that, a check got developed to detect cyclic dependencies between header files โ€” aka "include cycles". An include cycle happens when a header file A includes header file B, and B (or any subsequent included header file) includes back header file A, resulting in a circular dependency cycle. It's giving toxic relationship fr fr ๐Ÿ˜ญ

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. Which leads to faster compile times, improved code quality, and a more maintainable codebase overall. Plus, 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. Slay. ๐Ÿ’…

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 focus on potential issues within the project's own codebase. This limitation doesn't diminish the ability to detect #include cycles within the analyzed code. โœจ

Developers should carefully review any warnings or feedback provided by this solution. While the analysis aims to identify and prevent include cycles, there may be situations where exceptions or modifications are necessary. It's important to exercise judgment and consider the specific context of the codebase when making adjustments. ๐Ÿซถ

Options

  • IgnoredFilesList โ€” gives you a way to exclude specific files/headers from the warnings this check raises. You do it 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, which means no files are ignored by default. ๐ŸŽ€

misc-include-cleaner ๐Ÿงน

Checks for unused and missing includes. It only generates findings for the main file of a translation unit. The findings correspond to <https://clangd.llvm.org/design/include-cleaner>. iykyk ๐Ÿ’…

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 that disables insertion/removal of header files matching 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 sequences, detecting a potential attack as described in the Trojan Source attack <https://www.trojansource.codes>. Spooky season always, bestie. ๐Ÿ˜ญ

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 is confusing because they can change how you understand a whole statement line โ€” as described in Trojan Source <https://trojansource.codes>. Not it being sneaky like that ๐Ÿ’€

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 gets applied to a typedef/using of a pointer type rather than to the pointee. Because such constructs are often misleading to developers โ€” the const applies to the pointer rather than the pointee. It's giving false advertising. ๐Ÿ˜ค

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. That's because the const qualifier is less likely to be mistaken there โ€” 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; }
};

References

See the features disallowed in Fuchsia at <https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/cxx?hl=en> ๐ŸŒˆ


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. They come as a pair bestie, don't leave them hanging ๐Ÿซถ

The check does not flag implicitly-defined operators, deleted or private operators, or placement operators. ๐Ÿ™…โ€โ™€๏ธ

This check corresponds to CERT C++ Coding Standard rule DCL54-CPP. Overload allocation and deallocation functions as a pair in the same scope <https://www.securecoding.cert.org/confluence/display/cplusplus/DCL54-CPP.+Overload+allocation+and+deallocation+functions+as+a+pair+in+the+same+scope>.

๐ŸŒธ misc-no-recursion ๐ŸŒธ

Okay bestie, this one is on loop-detective duty ๐Ÿ’… โ€” it 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 shows you one example of a possible call graph loop (that's the recursion, babes).

References:

Limitations ๐Ÿ˜ญ

  • The check does not handle calls done through function pointers.
  • The check does not handle C++ destructors.

๐ŸŽ€ misc-non-copyable-objects ๐ŸŽ€

Heads up: cert-fio38-c redirects here as an alias for this check โœจ

It flags dereferences and non-pointer declarations of objects that are not meant to be passed by value โ€” think C FILE objects or POSIX pthread_mutex_t objects. Like, do not copy those, babe ๐Ÿ™ˆ

References:

This check corresponds to CERT C++ Coding Standard rule FIO38-C. Do not copy a FILE object <https://www.securecoding.cert.org/confluence/display/c/FIO38-C.+Do+not+copy+a+FILE+object>.


๐Ÿ‘‘ misc-non-private-member-variables-in-classes ๐Ÿ‘‘

Another alias situation: cppcoreguidelines-non-private-member-variables-in-classes redirects here as an alias for this check ๐Ÿ’œ

So what it does: finds classes that contain non-static data members plus user-declared non-static member functions, and diagnoses all data members declared with a non-public access specifier. The vibe is: those data members should be declared private and accessed through member functions instead of being exposed to derived classes or class consumers. Periodt.

Options

  • IgnoreClassesWithAllMemberVariablesBeingPublic โ€” When true, allows you to completely ignore classes if all the member variables in that class are declared with a public access specifier. Default is false.
  • IgnorePublicMemberVariables โ€” When true, allows you to ignore (not diagnose) all the member variables declared with a public access specifier. Default is false.

๐ŸŒˆ misc-override-with-different-visibility ๐ŸŒˆ

This one finds virtual function overrides with different visibility than the function in the base class. That 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. Also important, bestie: use of the using keyword is not considered as a 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
};

If the visibility is changed in this way, it can indicate bad design or a programming error. ๐Ÿšฉ

If a virtual function is private in a subclass but public in the base class, it can still be accessed from a pointer to the subclass if the pointer is converted to the base type. Probably private inheritance can be used instead.

A protected virtual function that is made public in a subclass may have valid use cases, but a similar (not exactly the same) effect can be achieved with the using keyword.

Options

  • DisallowedVisibilityChange โ€” Controls what kind of change to the visibility will be detected by the check. Possible values are any, widening, narrowing. For example the widening option will produce a warning only if the visibility is changed from more restrictive (private) to less restrictive (public). Default value is any.
  • CheckDestructors โ€” If true, the check does apply to destructors too. Otherwise destructors are ignored by the check. Default value is false.
  • CheckOperators โ€” If true, the check does apply to overloaded C++ operators (as virtual member functions) too. This includes other special member functions (like conversions) too. This option is probably useful only in rare cases because operators and conversions are not often virtual functions. Default value is false.
  • IgnoredFunctions โ€” This option can be used to ignore the check at specific functions. To configure this option, a semicolon-separated list of function names should be provided. The list can contain regular expressions, so this way it is possible to 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 for the usage of std::rand(). Real talk: pseudorandom number generators use mathematical algorithms to produce a sequence of numbers with good statistical properties, but the numbers produced are not genuinely random. The std::rand() function takes a seed (a number), runs a mathematical operation on it and returns the result. By manipulating the seed, the result can be predictable ๐Ÿ˜ญ not very slay of it.

References:

This check corresponds to the CERT C Coding Standard rules MSC30-C. Do not use the rand() function for generating pseudorandom numbers <https://wiki.sei.cmu.edu/confluence/display/c/MSC30-C.+Do+not+use+the+rand%28%29+function+for+generating+pseudorandom+numbers> and MSC50-CPP. Do not use std::rand() for generating pseudorandom numbers <https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC50-CPP.+Do+not+use+std%3A%3Arand%28%29+for+generating+pseudorandom+numbers>.


๐Ÿ’€ misc-redundant-expression ๐Ÿ’€

Detects redundant expressions which are typically errors due to copy-paste. We've all been there ๐Ÿ™ˆ

Depending on the operator, expressions may be:

  • redundant,
  • always true,
  • always false,
  • always a constant (zero or one).

Examples:

((x+1) | (x+1))                   // (x+1) is redundant
(p->x == p->x)                    // always true
(p->x < p->x)                     // always false
(speed - speed + 1 == 12)         // speed - speed is always zero
int b = a | 4 | a                 // identical expr on both sides
((x=1) | (x=1))                   // expression is identical
(DEFINE_1 | DEFINE_1)             // same macro on the both sides
((DEF_1 + DEF_2) | (DEF_1+DEF_2)) // expressions differ in spaces only

Floats are handled too, except in the case that NaNs are checked like so:

int TestFloat(float F) {
  if (F == F)               // Identical float values used
    return 1;
  return 0;
}

int TestFloat(float F) {
  // Testing NaN.
  if (F != F && F == F)     // does not warn
    return 1;
  return 0;
}

โœจ misc-static-assert โœจ

Alias alert: cert-dcl03-c redirects here as an alias for this check ๐Ÿ’ซ

It replaces assert() with static_assert() if the condition is evaluable at compile time. And why is that iconic? Because the condition of static_assert() is evaluated at compile time, which is safer and more efficient. Ate. ๐Ÿ’…


๐Ÿฆ‹ misc-throw-by-value-catch-by-reference ๐Ÿฆ‹

Okay so cert-err09-cpp and cert-err61-cpp redirect here as aliases of this check.

It finds violations of the rule "Throw by value, catch by reference" presented for example in "C++ Coding Standards" by H. Sutter and A. Alexandrescu, as well as the CERT C++ Coding Standard rule ERR61-CPP. Catch exceptions by lvalue reference <https://wiki.sei.cmu.edu/confluence/display/cplusplus/ERR61-CPP.+Catch+exceptions+by+lvalue+reference>.

Exceptions (the chill ones, they don't get flagged):

  • Throwing string literals will not be flagged despite being a pointer. They are not susceptible to slicing and the usage of string literals is idiomatic.
  • Catching character pointers (char, wchar_t, unicode character types) will not be flagged to allow catching string literals.
  • Moved named values will not be flagged as not throwing an anonymous temporary. In this case we can be sure that the user knows that the object can't be accessed outside catch blocks handling the error.
  • Throwing function parameters will not be flagged as not throwing an anonymous temporary. This allows helper functions for throwing.
  • Re-throwing caught exception variables will not be flagged as not throwing an anonymous temporary. Although this can usually be done by just writing throw; it happens often enough in real code.

Options

  • CheckThrowTemporaries โ€” Triggers detection of violations of the CERT recommendation ERR09-CPP. Throw anonymous temporaries. Default is true.
  • WarnOnLargeObject โ€” Also warns for any large, trivial object caught by value. Catching a large object by value is not dangerous but affects the performance negatively. The maximum size of an object allowed to be caught without warning can be set using the MaxSize option. Default is false.
  • MaxSize โ€” Determines the maximum size of an object allowed to be caught without 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, and definitions with a good return type but wrong return statements. Get into it:

  • 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 ๐Ÿ’–

Find and replace unique_ptr::reset(release()) with std::move(). Chef's kiss, so clean.

Example:

std::unique_ptr<Foo> x, y;
x.reset(y.release()); -> x = std::move(y);

If y is already an rvalue, bestie, we do not add std::move(). Rude to move something that's already moving, fr. ๐Ÿ’… And also โ€” plot twist โ€” x and y can also be std::unique_ptr<Foo>*. Slay.

๐Ÿ’– Options ๐Ÿ’–

  • IncludeStyle โ€” a string telling us which include-style we're vibing with: llvm or google. Default is llvm. Choose wisely โœจ

๐Ÿฆ‹ misc-unused-alias-decls ๐Ÿฆ‹

Spots namespace alias declarations that literally nobody uses. It's giving declared but forgotten. ๐Ÿ’€

namespace my_namespace {
class C {};
}
namespace unused_alias = ::my_namespace;

๐ŸŽ€ misc-unused-parameters ๐ŸŽ€

Finds function parameters that never get touched. And ngl, an unused parameter is a whole red flag ๐Ÿšฉ โ€” it usually means there's a bug hiding in there (like when a totally different parameter gets used instead ๐Ÿ™ˆ).

The suggested fixes are either: comment the parameter name out, or yeet the parameter entirely โ€” but only if every caller of the function lives in the same translation unit and can be updated too. iykyk.

This check is basically cousins with the -Wunused-parameter compiler diagnostic, and you can use it to prep your codebase before turning that diagnostic on. By default tho, the check is more permissive (see StrictMode). ๐ŸŒธ

void a(int i) { /*some code that doesn't use `i`*/ }

// becomes

void a(int  /*i*/) { /*some code that doesn't use `i`*/ }
static void staticFunctionA(int i);
static void staticFunctionA(int i) { /*some code that doesn't use `i`*/ }

// becomes

static void staticFunctionA()
static void staticFunctionA() { /*some code that doesn't use `i`*/ }

โœจ Options โœจ

  • StrictMode โ€” when it's false (the default, bestie), the check just ignores trivially unused parameters โ€” like when the function body is empty (and for constructors, when there are no constructor initializers). The logic? An empty body means a human reader is gonna notice the unused param anyway, and there's basically nowhere for a bug to hide. So we let it slide. ๐Ÿซถ
  • IgnoreVirtual โ€” decides whether virtual method parameters get inspected at all. Set it to true to ignore them. Default is false.

๐ŸŒˆ misc-unused-using-decls ๐ŸŒˆ

Catches using declarations that nobody uses. No cap.

But heads up โ€” unused using declarations inside header files will not be diagnosed, because those are part of the header's public API. Leave them alone, they're working. ๐Ÿ’… Allowed header file extensions can be configured via the global option HeaderFileExtensions.

Example, bestie:

// main.cpp
namespace n { class C; }
using n::C;  // Never actually used.

๐Ÿ‘‘ misc-use-anonymous-namespace ๐Ÿ‘‘

Finds static functions or variables declared at global scope that could be moved into an anonymous namespace instead. Main character energy relocation. ๐Ÿงš

Anonymous namespaces are the "superior alternative" according to the C++ Standard, periodt. static was proposed for deprecation, but then got un-deprecated to keep C compatibility [1]. Also static is an overloaded term with different meanings in different contexts, so it can create confusion. It's giving chaos.

The following uses of static will not be diagnosed:

  • Functions or variables in header files, since anonymous namespaces in headers is considered an antipattern. Allowed header file extensions can be configured via the global option HeaderFileExtensions.
  • 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 โ€” https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012


๐Ÿ’œ misc-use-internal-linkage ๐Ÿ’œ

Detects variables, functions, and classes that could be marked static, or (in C++) moved into an anonymous namespace, to enforce internal linkage.

Why do we care? Because any entity that's only used inside a single file should get internal linkage. That hands the compiler more info, so it can better delete dead code and go feral with aggressive optimizations. ๐Ÿ”ฅ

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 hand you. Default is UseStatic.
    • None โ€” don't fix automatically, we're just looking. ๐Ÿ‘€
    • 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. The lambdas use value-capture, unless you explicitly asked for reference capture with std::ref or boost::ref. โœจ

It supports arbitrary callables โ€” member functions, function objects, free functions, and every variation thereof. Anything you can pass to the first argument of bind should be diagnosable. The only known case where a fix-it is unsupported right now is when the same placeholder is specified multiple times in the parameter list. (Yeah. That one. ๐Ÿ˜ญ)

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 it can result in larger object files and binaries because of type information that equivalent lambdas just wouldn't produce. So likeโ€ฆ glow down. ๐Ÿ“‰

๐Ÿ’… Options ๐Ÿ’…

  • PermissiveParameterList โ€” if this is set to true, the check will append auto&&... to the end of every placeholder parameter list. Without it, a fix-it can perform an incorrect transformation when the result of the bind gets used in the context of a type erased functor like 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. No time-traveling to C++11, bestie. โณ

๐ŸŒธ modernize-avoid-c-arrays ๐ŸŒธ

cppcoreguidelines-avoid-c-arrays redirects here as an alias for this check.

hicpp-avoid-c-arrays redirects here as an alias for this check.

Finds C-style array types and recommends std::array<> / std::vector<> instead. All types of C arrays are diagnosed. Every single one. No survivors. ๐Ÿ’€

For parameters of incomplete C-style array type, it would be better to use std::span / gsl::span as a replacement.

However โ€” fix-its are potentially dangerous in header files and are therefore not emitted right now. So we warn, but we don't touch. ๐Ÿ›‘

int a[] = {1, 2}; // warning: do not declare C-style arrays, use 'std::array' instead

int b[1]; // warning: do not declare C-style arrays, use 'std::array' instead

void foo() {
  int c[b[0]]; // warning: do not declare C VLA arrays, use 'std::vector' instead
}

template <typename T, int Size>
class array {
  T d[Size]; // warning: do not declare C-style arrays, use 'std::array' instead

  int e[1]; // warning: do not declare C-style arrays, use 'std::array' instead
};

array<int[4], 2> d; // warning: do not declare C-style arrays, use 'std::array' instead

using k = int[4]; // warning: do not declare C-style arrays, use 'std::array' instead

However, the extern "C" code is ignored, since it's common to share such headers between C code and C++ code. We respect the crossover episode. ๐Ÿค

// Some header
extern "C" {

int f[] = {1, 2}; // not diagnosed

int j[1]; // not diagnosed

inline void bar() {
  {
    int j[j[0]]; // not diagnosed
  }
}

}

Similarly, the main() function is ignored. Its second and third parameters can be either char* argv[] or char** argv, but cannot be std::array<>. main() gets a pass, iconic behaviour. ๐Ÿ‘‘

๐ŸŽ€ 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 the length can't be deduced โ€” those resemble pointers, like what you see 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 whole variety of different conversions (const_cast, static_cast, reinterpret_cast, or some combination of them). That's what makes them dangerous โ€” the intent isn't clear, and they can silently perform unsafe conversions between incompatible types. It's giving mystery meat. ๐Ÿ˜ญ

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. All of them. Every last one. ๐Ÿšฉ

Exception handling with throw and catch should be used instead. That's the vibe, bestie. โœจ

โœจ References โœจ

This one's just the receipts for the CERT C++ Coding Standard rule ERR52-CPP, Do not use setjmp() or longjmp() โ€” you can go read the whole thing at https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=88046492. ๐Ÿซถ


๐ŸŒˆ modernize-avoid-variadic-functions ๐ŸŒˆ

okay so this check finds all function definitions โ€” but not declarations, bestie, read the fine print โ€” of C-style variadic functions. You know, the ones with the ... doing the absolute most. ๐Ÿ™ˆ

And the take? Instead of C-style variadic functions, you should be using a C++ function parameter pack. That's the whole vibe. periodt. โœจ

References

CERT C++ Coding Standard rule DCL50-CPP, Do not define a C-style variadic function โ€” the full lore lives at https://www.securecoding.cert.org/confluence/display/cplusplus/DCL50-CPP.+Do+not+define+a+C-style+variadic+function. ๐Ÿ’…


๐ŸŒธ modernize-concat-nested-namespaces ๐ŸŒธ

This check catches you using nested namespaces like namespace a { namespace b { ... } } and is like "bestieโ€ฆ we have a shorter way now" ๐Ÿ’€ โ€” it suggests switching to the way more concise syntax that showed up in C++17: namespace a::b { ... }. So clean, so slay.

Heads up: inline namespaces are not modified. They get left alone. ๐ŸŽ€

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

See how the nesting just collapses into ::? iconic. ๐Ÿ˜Œ


๐Ÿฆ‹ modernize-deprecated-headers ๐Ÿฆ‹

Some headers from the C library got deprecated in C++ and are genuinely no longer welcome in C++ codebases โ€” like, they're on the guest list of 2009 and we've moved on. Some of them literally have no effect in C++ at all. For the full details, go read the C++14 Standard [depr.c.headers] section. ๐Ÿ“–

What this check does: it replaces C standard library headers with their C++ alternatives, and yeets the redundant ones.

// C++ source file...
#include <assert.h>
#include <stdbool.h>

// becomes

#include <cassert>
// No 'stdbool.h' here.
Important note: the Standard doesn't guarantee that the C++ headers declare all the same functions in the global namespace. So this check, in its current form, can break code that uses library symbols from the global namespace. fr fr, be careful. ๐Ÿ˜ญ

Here's the full roster of headers it cares about:

  • <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 standard you've specified is older than C++11, the check will only replace headers that were deprecated before C++11. Otherwise โ€” every single header in that list above. no exceptions, bestie. ๐Ÿ’…

And these headers? They don't have any effect in C++ at all:

  • <iso646.h>
  • <stdalign.h>
  • <stdbool.h>

Also! The checker ignores include directives sitting inside extern "C" { ... } blocks, because a library might genuinely want to expose some API for both C and C++ libraries. Respect. ๐Ÿซถ

// C++ source file...
extern "C" {
#include <assert.h>  // Left intact.
#include <stdbool.h> // Left intact.
}

Options

  • CheckHeaderFile โ€” clang-tidy literally cannot know whether the header file included by the currently analyzed C++ source file is also included by any other C source files. So, to skip the false-positives and the wrong fixit-hints, it just doesn't emit reports into header files. You can set this option to true if you know that the header files in your project are only used by C++ source files. Default is false.

๐Ÿ‘‘ modernize-deprecated-ios-base-aliases ๐Ÿ‘‘

This one spots you using the deprecated member types of std::ios_base, and swaps the ones that have a non-deprecated equivalent. Glow-up time:

  • 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 takes your for(...; ...; ...) loops and converts them into the shiny new range-based loops from C++11. It's giving "we don't do that anymore, we do this now". โœจ

Three kinds of loops can get 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 shows up elsewhere in the loop, we lower our confidence in the transformation โ€” because the risk of changing semantics goes up. Transformations for these loops are marked as risky, and so they'll only be converted if the minimum required confidence level is set to risky.

int arr[10][20];
int l = 5;

for (int j = 0; j < 20; ++j)
  int k = arr[l][j] + l; // using l outside arr[l] is considered risky

for (int i = 0; i < obj.getVector().size(); ++i)
  obj.foo(10); // using 'obj' is considered risky
Peep 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 gets marked as reasonable, and so it'll be converted if the required confidence level is set to reasonable (the default) or lower.

// using size() is considered reasonable
for (int i = 0; i < container.size(); ++i)
  cout << container[i];

safe

Any other loops that don't match the criteria above for being marked risky or reasonable get marked safe, and so they'll be converted if the required confidence level is set to safe or lower.

int arr[] = {1,2,3};

for (int i = 0; i < 3; ++i)
  cout << arr[i];

Example

Original:

const int N = 5;
int arr[] = {1,2,3,4,5};
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);

// safe conversion
for (int i = 0; i < N; ++i)
  cout << arr[i];

// reasonable conversion
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
  cout << *it;

// reasonable conversion
for (vector<int>::iterator it = begin(v); it != end(v); ++it)
  cout << *it;

// reasonable conversion
for (vector<int>::iterator it = std::begin(v); it != std::end(v); ++it)
  cout << *it;

// reasonable conversion
for (int i = 0; i < v.size(); ++i)
  cout << v[i];

// reasonable conversion
for (int i = 0; i < size(v); ++i)
  cout << v[i];

After applying the check with the minimum confidence level set to reasonable (default):

const int N = 5;
int arr[] = {1,2,3,4,5};
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);

// safe conversion
for (auto & elem : arr)
  cout << elem;

// reasonable conversion
for (auto & elem : v)
  cout << elem;

// reasonable conversion
for (auto & elem : v)
  cout << elem;

Reverse Iterator Support

The converter is also capable of transforming iterator loops that use rbegin and rend to loop backwards over a container. Out of the box, this will automatically happen in C++20 mode using the ranges library โ€” but the check can be configured to work without C++20 by specifying a function to reverse a range, and optionally the header file where that function lives. ๐Ÿงš

Options

  • UseCxx20ReverseRanges โ€” when set 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, which are outlined by the cases below. ๐Ÿ™ˆ

Comments inside loop headers

Comments inside the original loop header are ignored and deleted when transformed. ๐Ÿ’€

for (int i = 0; i < N; /* This will be deleted */ ++i) { }

Range-based loops evaluate end() only once

Okay bestie, this is the โœจ important โœจ one. The C++11 range-based for loop calls .end() only once, during the initialization of the loop. So if your original loop was calling .end() after every single iteration, the semantics of the transformed loop might literally not be the same. ๐Ÿ’€

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

And like, as we said above, calling member functions of the container inside the loop body is considered risky. If the member function you call modifies the container, the converted loop's semantics will differ because .end() only gets called once now. ๐Ÿ™ˆ

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

So the original code up there prints out the contents of the container including the newly added element, while the converted loop (below) will only print the original contents and not the newly added element. Not the same vibe at all. ๐Ÿ˜ญ

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 affected if .end() has side effects. For example, if calls to .end() are logged, the semantics will change in the transformed loop if .end() was originally called after each iteration.

iterator end() {
  num_of_end_calls++;
  return container.end();
}

Overloaded operator->() with side effects

Same energy, different operator. If operator->() was overloaded to have side effects (like logging, bestie), the semantics will change. If the iterator's operator->() was used in the original loop, it gets replaced with <container element>.<member> instead, because of the implicit dereference that's part of the range-based for loop. So any side effect of the overloaded operator->() justโ€ฆ doesn't happen 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

So most of this check's risk analysis is dedicated to figuring out whether the iterator or container was modified inside the loop โ€” but you can lowkey circumvent that analysis by accessing and modifying the container through a pointer or reference. ๐Ÿ‘€

If the container were used directly instead of through the pointer or reference, the transformation below would've only been applied at the risky level, since calling a member function of the container is considered risky. But the check can't identify expressions associated with the container that are different from the one used in the loop header โ€” so the transformation below ends up being performed at the safe level. Wild, right?

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 are only available since OpenMP 5, so pls don't use this check on code with a compatibility requirement of OpenMP prior to version 5. And it is intentional that this check does not make any attempts to exclude incorrect diagnostics on OpenMP for loops prior to OpenMP 5. No cap. ๐Ÿงš

To prevent this check from being applied to (and breaking) OpenMP for loops, while still being applied to non-OpenMP for loops, the recommended move is to use NOLINT (see Suppressing Undesired Diagnostics <#clang-tidy-nolint>) on the specific for loops.


modernize-macro-to-enum

Replaces groups of adjacent macros with an unscoped anonymous enum. Using an unscoped anonymous enum ensures that everywhere the macro token was used previously, the enumerator name may be safely used. Iconic behaviour honestly. ๐Ÿ‘‘

This check can be used to enforce the C++ core guideline Enum.1: Prefer enumerations over macros <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#enum1-prefer-enumerations-over-macros>, within the constraints outlined below.

Potential macros for replacement must meet the following constraints:

  • Macros must expand only to integral literal tokens or expressions of literal tokens. The expression may contain any of the unary operators -, +, ~ or !, any of the binary operators ,, -, +, *, /, %, &, |, ^, <, >, <=, >=, ==, !=, ||, &&, <<, >> or <=>, the ternary operator ?: and its GNU extension <https://gcc.gnu.org/onlinedocs/gcc/Conditionals.html>. Parenthesized expressions are also recognized. This recognizes most valid expressions. In particular, expressions with 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.)
  • Macros must not be defined adjacent to other preprocessor directives.
  • Macros must not be used in any conditional preprocessing directive.
  • Macros must not be used as arguments to other macros.
  • Macros must not be undefined.
  • Macros must be defined at the top-level, not inside any declaration or definition.

Each cluster of macros meeting the above constraints is presumed to be a set of values suitable for replacement by an anonymous enum. From there, a developer can give the anonymous enum a name and continue refactoring to a scoped enum if desired. Comments on the same line as a macro definition, or between subsequent macro definitions, are preserved in the output. No formatting is assumed in the provided replacements, although clang-tidy can optionally format all fixes. ๐Ÿ’…

Warning: Initializing expressions are assumed to be valid initializers for an enum. C requires that enum values fit into an int, but this may not be the case for some accepted constant expressions. For instance 1 << 40 will not fit into an int when the size of an int is 32 bits.

Examples:

#define RED   0xFF0000
#define GREEN 0x00FF00
#define BLUE  0x0000FF

#define TM_NONE (-1) // No method selected.
#define TM_ONE 1    // Use tailored method one.
#define TM_TWO 2    // Use tailored method two.  Method two
                    // is preferable to method one.
#define TM_THREE 3  // Use tailored method three.

becomes

enum {
RED = 0xFF0000,
GREEN = 0x00FF00,
BLUE = 0x0000FF
};

enum {
TM_NONE = (-1), // No method selected.
TM_ONE = 1,    // Use tailored method one.
TM_TWO = 2,    // Use tailored method two.  Method two
                    // is preferable to method one.
TM_THREE = 3  // Use tailored method three.
};

modernize-make-shared

This check finds the creation of std::shared_ptr objects by explicitly calling the constructor and a new expression, and replaces it with a call to std::make_shared. So much cleaner, fr fr. โœจ

auto my_ptr = std::shared_ptr<MyPair>(new MyPair(1, 2));

// becomes

auto my_ptr = std::make_shared<MyPair>(1, 2);

This check also finds calls to std::shared_ptr::reset() with a new expression, and replaces it with 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

This check finds the creation of std::unique_ptr objects by explicitly calling the constructor and a new expression, and replaces it with a call to std::make_unique, introduced in C++14. ๐ŸŽ€

auto my_ptr = std::unique_ptr<MyPair>(new MyPair(1, 2));

// becomes

auto my_ptr = std::make_unique<MyPair>(1, 2);

This check also finds calls to std::unique_ptr::reset() with a new expression, and replaces it 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

Replaces nested std::min and std::max calls with an initializer list where applicable. Nested calls are so 2011 bestie, we're doing initializer lists now. ๐Ÿ’…

For instance, consider the following code:

int a = std::max(std::max(i, j), k);

The check will transform the above code to:

int a = std::max({i, j, k});

๐Ÿ’… Performance Considerations ๐Ÿ’…

okay so real talk bestie โ€” this check does make your code cleaner and way more readable, but it can lowkey tank your performance for non-trivial types, because it has to copy objects into the initializer list. ๐Ÿ˜ญ

to dodge that whole situation, the move 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 that says which include-style you're using, either llvm or google. default is llvm.
  • IgnoreNonTrivialTypes โ€” a boolean that says whether to ignore non-trivial types. default is true.
  • IgnoreTrivialTypesOfSizeAbove โ€” an integer saying the size (in bytes) above which trivial types get ignored. default is 32.

๐Ÿฆ‹ modernize-pass-by-value ๐Ÿฆ‹

so now that we have move semantics in the language, and the standard library got move constructors added for a bunch of types, it's actually way more interesting to take an argument directly by value instead of by const-reference and then copying. this check lets the compiler do its thing and pick the best way to construct the copy. periodt. ๐Ÿ’…

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 value gets constructed:

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: rn, only constructors get transformed to make use of pass-by-value. contributions that handle other situations are welcome! ๐Ÿซถ

๐ŸŒธ Pass-by-value in constructors ๐ŸŒธ

this replaces the uses of const-references constructor parameters that get copied into class fields. the parameter then gets moved with std::move().

since std::move() is a library function declared in <utility>, you might need to add that include. the check will add the include directive when it's necessary. no cap. โœจ

#include <string>

class Foo {
public:
-  Foo(const std::string &Copied, const std::string &ReadOnly)
-    : Copied(Copied), ReadOnly(ReadOnly)
+  Foo(std::string Copied, const std::string &ReadOnly)
+    : Copied(std::move(Copied)), ReadOnly(ReadOnly)
   {}

private:
  std::string Copied;
  const std::string &ReadOnly;
};

std::string get_cwd();

void f(const std::string &Path) {
  // The parameter corresponding to 'get_cwd()' is move-constructed. By
  // using pass-by-value in the Foo constructor we managed to avoid a
  // copy-construction.
  Foo foo(get_cwd(), Path);
}

if the parameter gets used more than once, no transformation happens โ€” because moved objects are in an undefined state. which means this next chunk gets left completely untouched:

#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 a situation where the generated code can be straight-up wrong: 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"
}

๐Ÿงš Note about delayed template parsing ๐Ÿงš

when delayed template parsing is enabled, constructors that are part of templated contexts โ€” templated constructors, constructors in class templates, constructors of inner classes of template classes, etc. โ€” do not get transformed. delayed template parsing is enabled by default on Windows as a Microsoft extension: Clang Compiler User's Manual - Microsoft extensions https://clang.llvm.org/docs/UsersManual.html#microsoft-extensions

delayed template parsing can be enabled using the -fdelayed-template-parsing flag and disabled using -fno-delayed-template-parsing.

example:

 template <typename T> class C {
   std::string S;

 public:
=  // using -fdelayed-template-parsing (default on Windows)
=  C(const std::string &S) : S(S) {}

+  // using -fno-delayed-template-parsing (default on non-Windows systems)
+  C(std::string S) : S(std::move(S)) {}
 };

See also: for more info about the pass-by-value idiom, read: Want Speed? Pass by Value https://web.archive.org/web/20140205194657/http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/


โœจ Options โœจ

  • IncludeStyle โ€” a string specifying which include-style is used, 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.

๐ŸŽ€ modernize-raw-string-literal ๐ŸŽ€

this check selectively replaces string literals that contain escaped characters with raw string literals. iconic behaviour honestly. ๐Ÿฆ‹

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 these escapes can cause the string to get converted to a raw string literal: \\, \', \", \?, and octal or hexadecimal escapes for printable ASCII characters. ๐Ÿ‘‘

a string literal containing only escaped newlines is a super common way of writing lines of text output. introducing physical newlines with raw string literals in this case is likely to mess with readability, so these string literals get left unchanged. ๐Ÿ™ˆ

an escaped horizontal tab, form feed, or vertical tab prevents the string literal from being converted. and the presence of a horizontal tab, form feed or vertical tab in source code is genuinely not visually obvious at all. ๐Ÿ˜ญ


โœจ Options โœจ

  • DelimiterStem โ€” 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 โญ

find and remove redundant void argument lists. bye bestie, you will not be missed. ๐Ÿ’…

examples:

  • 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 was done by the copy-constructor and the assignment operator โ€” gets changed to match std::unique_ptr usage by using explicit calls to std::move().

migration example:

-void take_ownership_fn(std::auto_ptr<int> int_ptr);
+void take_ownership_fn(std::unique_ptr<int> int_ptr);

 void f(int x) {
-  std::auto_ptr<int> a(new int(x));
-  std::auto_ptr<int> b;
+  std::unique_ptr<int> a(new int(x));
+  std::unique_ptr<int> b;

-  b = a;
-  take_ownership_fn(b);
+  b = std::move(a);
+  take_ownership_fn(std::move(b));
 }

since std::move() is a library function declared in <utility>, you may need to add this include. the check will add the include directive when it's necessary. โœจ


โš ๏ธ Limitations โš ๏ธ

  • if headers modification isn't activated, or if a header isn't allowed to be changed, this check will produce broken code (compilation error) โ€” where the headers' code stays unchanged while the code using them gets changed. ๐Ÿ’€
  • Client code that declares a reference to an std::auto_ptr that comes from code you literally cannot migrate (think: a header from some 3rd-party library you have zero control over) will blow up with a compilation error after migration. Why? Because the type of the reference gets changed to std::unique_ptr, but the type the library hands back doesn't change โ€” so you end up binding a reference to std::unique_ptr from an std::auto_ptr. ๐Ÿ˜ญ This pattern is giving nonsense anyway, and usually std::auto_ptrs are stored by value (like, otherwise what is even the point of using them instead of a reference or a pointer? bestie, be fr).
// <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? Not modified. 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 telling the check which include-style is in play: llvm or google. Default is llvm. ๐ŸŽ€

๐Ÿ’– modernize-replace-disallow-copy-and-assign-macro ๐Ÿ’–

Finds macro expansions of DISALLOW_COPY_AND_ASSIGN(Type) and swaps them out for a deleted copy constructor plus a deleted assignment operator. slay.

Back before the delete keyword existed (pre-C++11 era, ancient history fr), the common move was to declare a copy constructor and an assignment operator as private members. That effectively made them unusable to a class's public API.

But then delete dropped in C++11 and we can just... abandon that whole private-access situation and delete the methods entirely. iconic.

So when you run this check on code like this:

class Foo {
private:
  DISALLOW_COPY_AND_ASSIGN(Foo);
};

It gets transformed into this:

class Foo {
private:
  Foo(const Foo &) = delete;
  const Foo &operator=(const Foo &) = delete;
};

๐ŸŒธ Limitations ๐ŸŒธ

  • Heads up bestie: notice that the migration example above leaves the private access specification untouched. If you want warnings for deleted functions sitting in private sections, you might wanna run the check modernize-use-equals-delete to get those. ๐Ÿซถ

โœจ Options โœจ

  • MacroName โ€” a string specifying the macro name whose expansion will be replaced. Default is DISALLOW_COPY_AND_ASSIGN. ๐Ÿ’…

See: <https://en.cppreference.com/w/cpp/language/function#Deleted_functions>


๐Ÿฆ‹ modernize-replace-random-shuffle ๐Ÿฆ‹

This check goes hunting for occurrences of std::random_shuffle and replaces them with std::shuffle. Because in C++17, std::random_shuffle is no longer available โ€” so yeah, we gotta replace it. no cap. ๐Ÿ˜ค

Below are two examples of what kind of occurrences get found, and what they get replaced with.

std::vector<int> v;

// First example
std::random_shuffle(vec.begin(), vec.end());

// Second example
std::random_shuffle(vec.begin(), vec.end(), randomFunc);

Both of these get replaced with:

std::shuffle(vec.begin(), vec.end(), std::mt19937(std::random_device()()));

The second example also receives a warning that randomFunc is no longer supported in the same way as before โ€” so if the user wants that same functionality, they're gonna need to change the implementation of the randomFunc themselves.

One thing to be aware of here: std::random_device is quite expensive to initialize. So if you're using this code in a performance-critical place, you probably wanna initialize it somewhere else. ๐Ÿ”ฅ

Another thing: the seeding quality of the suggested fix is honestly quite poor โ€” std::mt19937 has an internal state of 624 32-bit integers, but it's only seeded with a single integer. So if you require higher quality randomness, you should consider seeding better, like for example:

std::shuffle(v.begin(), v.end(), []() {
  std::mt19937::result_type seeds[std::mt19937::state_size];
  std::random_device device;
  std::uniform_int_distribution<typename std::mt19937::result_type> dist;
  std::generate(std::begin(seeds), std::end(seeds), [&] { return dist(device); });
  std::seed_seq seq(std::begin(seeds), std::end(seeds));
  return std::mt19937(seq);
}());

โœจ modernize-return-braced-init-list โœจ

Replaces explicit calls to the constructor in a return with a braced initializer list. This way the return type isn't needlessly duplicated in both the function definition and the return statement. the way thatโ€ฆ efficient ๐Ÿ’…

Foo bar() {
  Baz baz;
  return Foo(baz);
}

// transforms to:

Foo bar() {
  Baz baz;
  return {baz};
}

๐ŸŽ€ modernize-shrink-to-fit ๐ŸŽ€

Replaces copy-and-swap tricks on shrinkable containers with the shrink_to_fit() method call.

The shrink_to_fit() method is more readable and more effective than the copy and swap trick for reducing the capacity of a shrinkable container. Note that the shrink_to_fit() method is only available in C++11 and up. so like, check your standard, bestie ๐Ÿซถ


๐ŸŒˆ modernize-type-traits ๐ŸŒˆ

Converts standard library type traits of the form traits<...>::type and traits<...>::value into traits_t<...> and traits_v<...> respectively. obsessed.

For example:

std::is_integral<T>::value
std::is_same<int, float>::value
typename std::add_const<T>::type
std::make_signed<unsigned>::type

Would be converted into:

std::is_integral_v<T>
std::is_same_v<int, float>
std::add_const_t<T>
std::make_signed_t<unsigned>

โœจ Options โœจ

  • IgnoreMacros โ€” if true, don't diagnose traits defined in macros. ๐Ÿ’–

Note: fixes will never be emitted for code inside of macros.

#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). not yet, anyway ๐Ÿ™ˆ


๐Ÿ’œ modernize-unary-static-assert ๐Ÿ’œ

The check diagnoses any static_assert declaration with an empty string literal, and provides a fix-it note to replace the declaration with a single-argument static_assert declaration.

The check is only applicable for C++17 and later code. periodt.

The following code:

void f_textless(int a) {
  static_assert(sizeof(a) <= 10, "");
}

is replaced by:

void f_textless(int a) {
  static_assert(sizeof(a) <= 10);
}

๐Ÿ‘‘ modernize-use-auto ๐Ÿ‘‘

This check is responsible for using the auto type specifier on variable declarations to improve code readability and maintainability. main character energy. For example:

std::vector<int>::iterator I = my_container.begin();

// transforms to:

auto I = my_container.begin();

The auto type specifier will only be introduced in situations where the variable type matches the type of the initializer expression. In other words, auto should deduce the same type that was originally spelled in the source. But โ€” and this is the important part โ€” not every situation should be transformed:

int val = 42;
InfoStruct &I = SomeObject.getInfo();

// Should not become:

auto val = 42;
auto &I = SomeObject.getInfo();

In this example, using auto for builtins doesn't improve readability. In other situations it makes the code less self-documenting, impairing readability and maintainability. As a result, auto is only introduced in the specific situations described below. ๐Ÿซถ

โญ Iterators โญ

Iterator type specifiers tend to be long and used constantly, especially in loop constructs. Since the functions generating iterators have a common format, the type specifier can be replaced without obscuring the meaning of the code โ€” while improving readability and maintainability. slay.

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 the following examples:
// The following direct uses of iterator types will be transformed.
std::vector<int>::iterator I = MyVec.begin();
{
  using namespace std;
  list<int>::iterator I = MyList.begin();
}

// The type specifier for J would transform to auto since it's a typedef
// to a standard iterator type.
typedef std::map<int, std::string>::const_iterator map_iterator;
map_iterator J = MyMap.begin();

// The following implementation-specific iterator type for which
// std::vector<int>::iterator could be a typedef would not be transformed.
__gnu_cxx::__normal_iterator<int*, std::vector> K = MyVec.begin();
  • The initializer for the variable being declared is not a braced initializer list. Otherwise, use of auto would cause the type of the variable to be deduced 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, once in the new expression. In this 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 as 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 ๐Ÿ’…

Okay so this is such a common pattern it hurts. When you declare a variable and initialize it with a cast, you end up writing the type twice โ€” once in the declaration, once inside the cast. Bestie. Why. In that case the declaration type can just become auto, and boom: readability and maintainability both ate. โœจ

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 โ€” think llvm::dyn_cast, boost::lexical_cast, and gsl::narrow_cast. ๐Ÿฆ‹ A call to a function template counts as "behaving like a cast" if the first template argument is explicit and is a type, and the function returns that type, or a pointer or reference to it. Slay, that's the whole rule.

Limitations ๐Ÿ™ˆ

  • If the initializer is an explicit conversion constructor, the check will not replace the type specifier even though it would be safe to do so.
  • User-defined iterators are not handled at this time.

Options ๐ŸŽ€

  • MinTypeNameLength โ€” if you set this to non-zero (default 5), the check ignores type names whose length is less than the option value. Heads up: this option only affects expressions, not iterators. Spaces between multi-lexeme type names (long int) count as one. And if RemoveStars (below) is true, then the *s in the type get counted as part of the type name too. ๐Ÿ‘‘
// MinTypeNameLength = 0, RemoveStars=0

int a = static_cast<int>(foo());            // ---> auto a = ...
// length(bool *) = 4
bool *b = new bool;                         // ---> auto *b = ...
unsigned c = static_cast<unsigned>(foo());  // ---> auto c = ...
// MinTypeNameLength = 5, RemoveStars=0

int a = static_cast<int>(foo());                 // ---> int  a = ...
bool b = static_cast<bool>(foo());               // ---> bool b = ...
bool *pb = static_cast<bool*>(foo());            // ---> bool *pb = ...
unsigned c = static_cast<unsigned>(foo());       // ---> auto c = ...
// length(long <on-or-more-spaces> int) = 8
long int d = static_cast<long int>(foo());       // ---> auto d = ...
// MinTypeNameLength = 5, RemoveStars=1

int a = static_cast<int>(foo());                 // ---> int  a = ...
// length(int * * ) = 5
int **pa = static_cast<int**>(foo());            // ---> auto pa = ...
bool b = static_cast<bool>(foo());               // ---> bool b = ...
bool *pb = static_cast<bool*>(foo());            // ---> auto pb = ...
unsigned c = static_cast<unsigned>(foo());       // ---> auto c = ...
long int d = static_cast<long int>(foo());       // ---> auto d = ...
  • RemoveStars โ€” if you set this to true (default is false), the check will yank the stars out of non-typedef pointer types when it swaps type names for auto. Otherwise the check leaves the stars right where they are. Like so: ๐ŸŒŸ
TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;

// RemoveStars = 0

auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName;

// RemoveStars = 1

auto my_first_pointer = new TypeName, my_second_pointer = new TypeName;

modernize-use-bool-literals ๐Ÿ’–

Finds integer literals that are being cast to bool. That's it, that's the whole job, and honestly? Iconic.

bool p = 1;
bool f = static_cast<bool>(1);
std::ios_base::sync_with_stdio(0);
bool x = p ? 1 : 0;

// transforms to

bool p = true;
bool f = true;
std::ios_base::sync_with_stdio(false);
bool x = p ? true : false;

Options ๐Ÿ“

  • IgnoreMacros โ€” if set to true, the check will not give warnings inside macros. Default is true.

modernize-use-constraints ๐Ÿงš

Replace std::enable_if with C++20 requires clauses. Glow-up of the century, no cap. โœจ

std::enable_if is a SFINAE mechanism for selecting the desired function or class template based on type traits or other requirements. But enable_if changes the meta-arity of the template, and it has other adverse side effects (https://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0225r0.html) in the code. C++20 introduces concepts and constraints as a cleaner, language-provided solution to get the same outcome. ๐Ÿ’…

This check finds some common std::enable_if patterns that can be replaced by C++20 requires clauses. The tool can replace some of these patterns automatically; otherwise, the tool will emit a diagnostic without a replacement. The tool can detect the following std::enable_if patterns:

  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 not currently supported by this tool either. ๐Ÿซถ

Below are some examples of code using std::enable_if:

// enable_if in function return type
template <typename T>
std::enable_if_t<T::some_trait, int> only_if_t_has_the_trait() { ... }

// enable_if in the trailing template parameter
template <typename T, std::enable_if_t<T::some_trait, int> = 0>
void another_version() { ... }

template <typename T>
typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
  return Obj{};
}

template <typename T, std::enable_if_t<T::some_trait, int> = 0>
struct my_class {};

The tool will replace the above code with:

// warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
template <typename T>
int only_if_t_has_the_trait() requires T::some_trait { ... }

// warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
template <typename T>
void another_version() requires T::some_trait { ... }

// The tool will emit a diagnostic for the following, but will
// not attempt to replace the code.
// warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
template <typename T>
typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
  return Obj{};
}

// The tool will not emit a diagnostic or attempt to replace the code.
template <typename T, std::enable_if_t<T::some_trait, int> = 0>
struct my_class {};
Note: system headers are not analyzed by this check. ๐Ÿšซ

modernize-use-default ๐Ÿ‘‘

This check has been renamed to modernize-use-equals-default. That's it. It moved, it rebranded, it's thriving. ๐Ÿ’…


modernize-use-default-member-init ๐ŸŒธ

This check converts constructors' member initializers into the new default member initializers from C++11. Other member initializers that match the default member initializer get removed. This can cut down repeated code, or let you use = default. Chef's kiss. ๐Ÿ˜Œ

struct A {
  A() : i(5), j(10.0) {}
  A(int i) : i(i), j(10.0) {}
  int i;
  double j;
};

// becomes

struct A {
  A() {}
  A(int i) : i(i) {}
  int i{5};
  double j{10.0};
};
Note: only converts member initializers for built-in types, enums, and pointers. The readability-redundant-member-init check will remove redundant member initializers for classes. ๐ŸŽ€

Options ๐Ÿฆ‹

  • UseAssignment โ€” if this option is set 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.

modernize-use-designated-initializers ๐Ÿ’œ

Finds initializer lists for aggregate types that could be written as designated initializers instead. Because listen: with plain initializer lists, it is so easy to introduce bugs when you add new fields in the middle of a struct or class type. Same confusion energy when you change the order of fields. ๐Ÿ˜ญ

C++20 supports the designated initializer syntax for aggregate types. Apply it, and you can always be sure aggregates are constructed correctly, because every variable being initialized is referenced by its name. Main character behaviour, honestly. โœจ

Example:

struct S { int i, j; };

is an aggregate type that should be initialized as

S s{.i = 1, .j = 2};

instead of

S s{1, 2};

which could easily become an issue when i and j are swapped in the declaration of S. ๐Ÿ™ˆ

Even when compiling in a language version older than C++20, depending on your compiler, designated initializers are potentially supported. Therefore, the check is by default restricted to C99/C++20 and above. Check out the options -Wc99-designator to get support for mixed designators in initializer list in C, and -Wc++20-designator for support of designated initializers in older C++ language modes. ๐Ÿ”ฅ

Options ๐ŸŒˆ

  • IgnoreMacros โ€” the 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 โญ

The check flags insertions to an STL-style container done by calling the push_back, push, or push_front methods with an explicitly-constructed temporary of the container element type. In that case, the corresponding emplace equivalent methods result in less verbose and potentially more efficient code. ๐Ÿ’…

Right now the check doesn't support insert. It also doesn't support insert functions for associative containers, because replacing insert with emplace may result in speed regression (https://htmlpreview.github.io/?https://github.com/HowardHinnant/papers/blob/master/insert_vs_emplace.html) โ€” but it might get support with some addition flag in the future. ๐Ÿ‘€

Okay so these three options โ€” ContainersWithPushBack, ContainersWithPush, and ContainersWithPushFront โ€” are literally just how you tell the check which container types do the push_back, push, and push_front things respectively. And the default line-up? Iconic, no notes:

  • 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 clocks when an emplace-like method is being used wrong, like calling emplace_back while also calling a constructor. That's giving "made a whole temporary for no reason," which needs at best a move and at worst a copy โ€” tragic honestly. Almost every emplace-like function in the STL is covered by this, with the one exception being try_emplace on std::map and std::unordered_map, because it lowkey behaves differently from all the others. Wanna add more containers? Use the EmplacyFunctions option โ€” as long as the container defines a value_type type, and the emplace-like functions construct a value_type object. ๐Ÿซถ

Before:

std::vector<MyClass> v;
v.push_back(MyClass(21, 37));
v.emplace_back(MyClass(21, 37));

std::vector<std::pair<int, int>> w;

w.push_back(std::pair<int, int>(21, 37));
w.push_back(std::make_pair(21L, 37L));
w.emplace_back(std::make_pair(21L, 37L));

After:

std::vector<MyClass> v;
v.emplace_back(21, 37);
v.emplace_back(21, 37);

std::vector<std::pair<int, int>> w;
w.emplace_back(21, 37);
w.emplace_back(21L, 37L);
w.emplace_back(21L, 37L);

By default, the check can yeet the 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 the TupleTypes option. Got custom make functions? That's the TupleMakeFunctions option. ๐Ÿ’…

The other whole situation is when we're passing arguments that are gonna 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");

Now here's the tea โ€” sometimes the transformation would be valid, but the code wouldn't be exception safe. In that case the calls to push_back will not be replaced. Periodt.

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 swapping it for emplace_back could cause a leak of that pointer if emplace_back would throw an exception before emplacement (like, say, not enough memory to add a new element). ๐Ÿ˜ญ

For more info read item 42 - "Consider emplacement instead of insertion." of Scott Meyers "Effective Modern C++". Bestie, it's a classic, go read it.

The default smart pointers that are considered are std::unique_ptr, std::shared_ptr, std::auto_ptr. Wanna specify other smart pointers or other classes? Use the SmartPointers option.

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. No C++11, no vibes. ๐Ÿ™ˆ

โœจ 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, e.g.
std::vector<std::string> v;
v.push_back("a"); // Ignored when IgnoreImplicitConstructors is `true`.

Default is false.

  • SmartPointers โ€” Semicolon-separated list of class names of custom smart pointers.
  • TupleTypes โ€” Semicolon-separated list 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 and some emplace-like method of the container. Example: vector::emplace_back. Those methods will be checked for improper use and the check will report 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. Slay. ๐Ÿ‘‘


modernize-use-equals-default ๐Ÿ’–

This check replaces default bodies of special member functions with = default;. And why do we care? Because explicitly defaulted function declarations unlock more optimization opportunities โ€” the compiler might treat explicitly defaulted functions as trivial. Main character behavior fr fr.

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. Give it time bestie, it's working on itself. ๐Ÿงš

โœจ Options โœจ

  • IgnoreMacros โ€” If set to true, the check will not give warnings inside macros and will ignore special members with bodies contain macros or preprocessor directives. Default is true.

modernize-use-equals-delete ๐ŸŽ€

This one identifies unimplemented private special member functions and recommends using = delete for them. Bonus: it recommends relocating any deleted member function from the private section to the public section. Glow-up.

Before C++11 existed, the main way to effectively "erase" a particular function was to declare it private without giving it a definition. That approach would give you either a compiler error (when you tried to call a private function) or a linker error (due to an undefined reference). Truly the dark ages.

But then C++11 arrived and a more conventional approach emerged: flag functions as = delete and keep them in the public section of the class. So chic.

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 the user is responsible for manually relocating functions to the public section. You got this. ๐Ÿ’ช

// 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 ๐ŸŒˆ

Replace comparisons between signed and unsigned integers with their safe C++20 std::cmp_* alternative, if available.

The check provides a replacement only for C++20 or later โ€” otherwise it highlights the problem and expects the user to fix it manually. No cap. ๐Ÿ˜ญ

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 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 in order to highlight at compile time which return values should not be ignored. The way that the compiler just tells you not to ignore it? Obsessed.

Member functions need to satisfy the following conditions to be considered by this check:

  • no [[nodiscard]], [[noreturn]], __attribute__((warn_unused_result)), [[clang::warn_unused_result]] nor [[gcc::warn_unused_result]] attribute,
  • non-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). ๐Ÿ‘€

๐ŸŒธ 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.
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 glow-up. ๐Ÿ’…

๐ŸŒธ Example ๐ŸŒธ

void foo() throw();
void bar() throw(int) {}

transforms to:

void foo() noexcept;
void bar() noexcept(false) {}
  • ReplacementString โ€” bestie, this lets you name a macro to use instead of noexcept. Super handy when you're babysitting legacy source that marks exception specs some other way. Heads up tho: fix-it hints only get 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, fr fr. Disable it and you get fix-it hints that completely rip out throwing dynamic exception specs, like throw(<something>), with no replacement text at all. Exception alert ๐Ÿšจ: destructors and delete operators are noexcept(true) by default, so those don't get the same treatment.

๐ŸŒธ 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. (Yes, that's a double negative, and yes, it's iconic. ๐Ÿ‘‘)


โœจ modernize-use-nullptr โœจ

Okay so this check converts null pointer constants (like NULL, 0) into the shiny new C++11 and C23 nullptr keyword. It's giving glow-up. ๐ŸŒˆ

๐ŸŒธ Example ๐ŸŒธ

void assignment() {
  char *a = NULL;
  char *b = 0;
  char c = 0;
}

int *ret_ptr() {
  return 0;
}

transforms to:

void assignment() {
  char *a = nullptr;
  char *b = nullptr;
  char c = 0;
}

int *ret_ptr() {
  return nullptr;
}

๐ŸŽ€ Options ๐ŸŽ€

  • IgnoredTypes โ€” a semicolon-separated list of regular expressions matching pointer types whose implicit casts get ignored. Default value: std::_CmpUnspecifiedParam::;^std::__cmp_cat::__unspec.
  • NullMacros โ€” a comma-separated list of macro names that get transformed right along with NULL. By default this check only swaps the NULL macro itself and skips any similar user-defined macros. ๐Ÿ™ˆ

๐ŸŒธ Example ๐ŸŒธ

#define MY_NULL (void*)0
void assignment() {
  void *p = MY_NULL;
}

transforms to:

#define MY_NULL NULL
void assignment() {
  int *p = nullptr;
}

...if the NullMacros option is set to MY_NULL.


โœจ modernize-use-override โœจ

This one adds override (introduced in C++11, our beloved) to overridden virtual functions, and yeets virtual off those functions because it's simply not required anymore. Slay. ๐Ÿ’…

Back in the day, virtual on non base class implementations was a lil hint to the reader that a function was virtual โ€” C++ compilers didn't use its presence to signify an overridden function. The vibes were doing all the work. ๐Ÿ˜ญ

Then C++11 dropped override and final so overridden functions could be marked properly, and their presence lets compilers verify that an overridden function actually overrides a base class implementation.

This is lowkey so useful because compilers can throw 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 โ€” set it to true and this check won't diagnose destructors. Default is false.
  • IgnoreTemplateInstantiations โ€” set it to true and this check ignores virtual function overrides that are part of template instantiations. Default is false.
  • AllowOverrideAndFinal โ€” set it to true and this check won't diagnose override as redundant with final. Useful when your code gets compiled by a compiler with warning/error checking flags that require override explicitly on overridden members, like gcc -Wsuggest-override/gcc -Werror=suggest-override. Default is false.
  • OverrideSpelling โ€” names a macro to use instead of override. Obsessed with this one if you maintain source that also needs to compile with a pre-C++11 compiler. ๐Ÿซถ
  • FinalSpelling โ€” names a macro to use instead of final. Same deal, same pre-C++11 compiler energy.
Note: for more info 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 swapped for a ranges version instead. Glow-up for your algorithms. โœจ

๐ŸŒธ Example ๐ŸŒธ

auto Iter1 = std::find(Items.begin(), Items.end(), 0);
auto AreSame = std::equal(Items1.cbegin(), Items1.cend(),
                          std::begin(Items2), std::end(Items2));

Transforms to:

auto Iter1 = std::ranges::find(Items, 0);
auto AreSame = std::ranges::equal(Items1, Items2);

๐Ÿ’œ Supported algorithms ๐Ÿ’œ

Calls to the following std library algorithms 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 gets fixed using the std::views::reverse adaptor.

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

Transforms to:

auto AreSame = std::ranges::equal(std::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 involving reverse ranges 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. It's giving upgrade. ๐Ÿ”ฅ

Fix-its are provided for single declarations of std::lock_guard, and a warning is emitted for multiple declarations of std::lock_guard that could be replaced with a single declaration of std::scoped_lock.

๐ŸŒธ Examples ๐ŸŒธ

Single std::lock_guard declaration:

std::mutex M;
std::lock_guard<std::mutex> L(M);

Transforms to:

std::mutex M;
std::scoped_lock L(M);

Single std::lock_guard declaration with std::adopt_lock:

std::mutex M;
std::lock(M);
std::lock_guard<std::mutex> L(M, std::adopt_lock);

Transforms to:

std::mutex M;
std::lock(M);
std::scoped_lock L(std::adopt_lock, M);

Multiple std::lock_guard declarations only emit warnings:

std::mutex M1, M2;
std::lock(M1, M2);
std::lock_guard Lock1(M1, std::adopt_lock); // warning: use single 'std::scoped_lock' instead of multiple 'std::lock_guard'
std::lock_guard Lock2(M2, std::adopt_lock); // note: additional 'std::lock_guard' declared here

๐Ÿ’€ Limitations ๐Ÿ’€

The check will not emit warnings if std::lock_guard is used implicitly via template parameter:

template <template <typename> typename Lock>
void TemplatedLock() {
  std::mutex M;
  Lock<std::mutex> L(M); // no warning
}

void instantiate() {
  TemplatedLock<std::lock_guard>();
}

๐ŸŽ€ Options ๐ŸŽ€

  • WarnOnSingleLocks โ€” when true, the check warns on single std::lock_guard declarations. Set this option to false if you only want warnings on multiple std::lock_guard declarations that can be replaced with a single std::scoped_lock. Default is true.
  • WarnOnUsingAndTypedef โ€” when true, the check emits 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 them with the simpler method when it's available. Notably, this will work with std::string and std::string_view. No more doing it the long way, bestie โ€” we're being efficient in 2024. ๐Ÿ’…๐ŸŒธ

Covered scenarios

okay bestie, here's the full receipts of exactly what this check catches and what it swaps in ๐Ÿ’…

  • u.find(v) == 0 โ†’ u.starts_with(v)
  • u.find(v, 0) == 0 โ†’ u.starts_with(v)
  • u.find(v, 0, v.size()) == 0 โ†’ u.starts_with(v)
  • u.rfind(v, 0) != 0 โ†’ !u.starts_with(v)
  • u.rfind(v, 0, v.size()) != 0 โ†’ !u.starts_with(v)
  • u.compare(0, v.size(), v) == 0 โ†’ u.starts_with(v)
  • u.substr(0, v.size()) == v โ†’ u.starts_with(v)
  • v != u.substr(0, v.size()) โ†’ !u.starts_with(v)
  • u.compare(u.size() - v.size(), v.size(), v) == 0 โ†’ u.ends_with(v)
  • u.rfind(v) == u.size() - v.size() โ†’ u.ends_with(v)

modernize-use-std-format

This one is so satisfying, fr fr. It takes calls to absl::StrFormat โ€” or literally any other function you point it at through the config options โ€” and rewrites them into C++20's std::format, or a different function if you set that option too. It tweaks the format string to match, and it also yeets the now-pointless std::string::c_str() and std::string::data() calls that were just hanging around for no reason ๐Ÿงš

Like, it takes lines that look like this:

return absl::StrFormat("The %s is %3d", description.c_str(), value);

and turns them into:

return std::format("The {} is {:3}", description, value);

Under the hood it's running the exact same format-string-conversion algorithm as modernize-use-std-print, and all the quirks and shortcomings of that algorithm โ€” especially how it behaves when macros get involved โ€” are documented over in that check's docs. So if something's weird, that's where the lore lives ๐Ÿ‘‘

Options

  • StrictMode โ€” when this is true, the check sprinkles in casts while converting from variadic functions, and when it's printing signed or unsigned integer types as the opposite signedness (and yes, that includes the fixed-width integer types from <cstdint>, plus ptrdiff_t, size_t and ssize_t). Why? To make sure the output still matches what you'd get from a simple wrapper around std::sprintf that took a C-style variable argument list. So with StrictMode on, this:
extern std::string strprintf(const char *format, ...);
int i = -42;
unsigned int u = 0xffffffff;
return strprintf("%u %d\n", i, u);

would get converted to:

return std::format("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));

and that's how the output keeps being the unsigned representation of -42 and the signed representation of 0xffffffff (which is often 4294967254 and -1 respectively). When it's false โ€” and false is the default โ€” those casts do not get added, which may cause a change in the output ๐Ÿ˜ญ Heads up: this option makes zero difference for the default value of StrFormatLikeFunctions, because absl::StrFormat takes a function parameter pack and is not a variadic function. No cap.

  • StrFormatLikeFunctions โ€” a semicolon-separated list of regular expressions matching the (fully qualified) names of the functions you want replaced. The requirement is that the first parameter holds the printf-style format string and the arguments to be formatted come immediately after it. 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 whatever StrFormatLikeFunctions set, 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 a #include directive can be added if it's required. If ReplacementFormatFunction is std::format then this option defaults to <format>, otherwise this option defaults to nothing and no #include directive gets added.

modernize-use-std-numbers

This check hunts down constants and function calls to math functions that could totally be replaced with C++20's mathematical constants from the numbers header, and it offers fix-it hints to do it ๐ŸŒธ

It does not match the use of variables holding that value โ€” instead, it offers a replacement for the definition of those variables. Sneaky, right? And function calls that match the pattern of how the constant is calculated get matched and replaced with the std::numbers constant. Macros are a special case too: the use of a macro gets replaced with the corresponding std::numbers constant, instead of touching the macro definition itself.

Here's the list of constants from the numbers header that 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, bestie ๐Ÿ’–

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, llvm or google. Default is llvm.

modernize-use-std-print

Okay so this one converts calls to printf, fprintf, absl::PrintF and absl::FPrintf into equivalent calls to C++23's std::print or std::println, whichever fits best, and it modifies the format string appropriately to match ๐Ÿฆ‹

Both the replaced functions and the replacement functions can be customised with configuration options. And every argument that's the result of a call to std::string::c_str() or std::string::data() gets that now-unnecessary call stripped out, in a similar manner to the readability-redundant-string-cstr check. Iconic behaviour, honestly.

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. Just so you know ๐Ÿ‘‘

Macros starting with PRI and __PRI from <inttypes.h> get expanded, escaping is handled, and adjacent strings are concatenated to form a single StringLiteral before the format string is converted. But use any other macros in the format string and you'll get a warning message emitted, and no conversion will be performed at all. And 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, but 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. Slay ๐Ÿ’…
  • Use of any unsupported flags or specifiers will cause the entire statement to be left alone and a warning to be emitted. The particular unsupported features are:
    • The %' flag for thousands separators.
    • The glibc extension %m.
  • 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 originalformat 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-trailing-return-type โœจ

Rewrites function and lambda signatures to use a trailing return type (introduced in C++11). This transformation is purely stylistic. The return type before the function name is replaced by auto and inserted after the function parameter list (and qualifiers). 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. ๐Ÿ’€

Given the following piece of code

struct S { long long value; };
S f(unsigned S) { return {S * 2}; }
class CC {
  int S;
  struct S m();
};
S CC::m() { return {0}; }

a careless rewrite would produce the following output:

struct S { long long value; };
auto f(unsigned S) -> S { return {S * 2}; } // error
class CC {
  int S;
  auto m() -> struct S;
};
auto CC::m() -> S { return {0}; } // error

This code fails to compile because the S in the context of f refers to the equally named function parameter. Similarly, the S in the context of m refers to the equally named class member. The check can currently only detect and avoid a clash with a function parameter name.

โœจ Options โœจ

  • TransformFunctions โ€” When set to true, function declarations will be transformed to use trailing return. Default 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. 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. Periodt. ๐Ÿ‘‘

// Non-transparent functor
std::map<int, std::string, std::greater<int>> s;

// Transparent functor.
std::map<int, std::string, std::greater<>> s;

// Non-transparent functor
using MyFunctor = std::less<MyType>;

It is not always a safe transformation though. 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.

โœจ 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.

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 using keyword.

โœจ 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 needs C++11 or higher to even run, bestie. Like, it literally will not function otherwise. ๐Ÿ’€

Options

  • IgnoreMacros โ€” if you set this to true, the check will not give warnings inside macros. Default is true.
  • IgnoreExternC โ€” if you set this to true, the check will not give warnings inside an extern "C" scope. Default is false.

mpi-buffer-deref

Okay so this one checks whether a buffer you hand to an MPI (Message Passing Interface) function is dereferenced enough. Buffers should come through as a single pointer or an array, periodt. But here's the tea: MPI function signatures declare their buffer types as void *, so you can absolutely slide in an under-dereferenced buffer โ€” a double pointer, a multidimensional array, whatever โ€” and the compiler will just sit there and say nothing. ๐Ÿ™ˆ No warning. Nada.

Examples:

// A double pointer is passed to the MPI function.
char *buf;
MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);

// A multidimensional array is passed to the MPI function.
short buf[1][1];
MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD);

// A pointer to an array is passed to the MPI function.
short *buf[1];
MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD);

mpi-type-mismatch

This check verifies that the buffer type and the MPI (Message Passing Interface) datatype actually match for the MPI functions you're using. Every MPI datatype defined by the MPI standard (3.1) gets checked by this one, no cap. User-defined typedefs, custom MPI datatypes and null pointer constants get skipped during verification, so don't come for this check about those. ๐Ÿ’…

Example:

// In this case, the buffer type matches MPI datatype.
char buf;
MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);

// In the following case, the buffer type does not match MPI datatype.
int buf;
MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);

objc-assert-equals

Sniffs out improper usages of XCTAssertEqual and XCTAssertNotEqual and swaps them out for XCTAssertEqualObjects or XCTAssertNotEqualObjects. ๐ŸŽ€

The whole point? It makes your tests less fragile. A ton of tests improperly lean on pointer equality for strings that have equal values, and that assumption is not guaranteed by the language, bestie. It's giving false confidence.


objc-avoid-nserror-init

Catches improper initialization of NSError objects. ๐Ÿšฉ

Per Apple developer documentation, you should always use the factory method errorWithDomain:code:userInfo: to create new NSError objects instead of [NSError alloc] init]. If you don't? You get a warning message at runtime. Not the vibe.

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. And this is a real problem, not a fake one: the category implementation will override any -dealloc in the class implementation, which can potentially cause issues. ๐Ÿ˜ญ

Here's why that's scary โ€” 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. Run, don't walk.


objc-forbidden-subclassing

Finds Objective-C classes that are subclasses of classes which are simply 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>

Options

  • ForbiddenSuperClassNames โ€” semicolon-separated list of names of Objective-C classes which do not support subclassing.

Defaults to ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView.


objc-missing-hash

Finds Objective-C implementations that implement -isEqual: without also appropriately implementing -hash. ๐Ÿ‘€

Apple documentation highlights that objects which are equal must have the same hash value: <https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc>

Heads up though: the check only verifies the presence of -hash in scenarios where its omission could result in unexpected behavior. Verifying the actual implementation of -hash? That's on you, the developer โ€” like, add unit tests to verify the implementation. This check is not your babysitter. ๐Ÿ’…


objc-nsdate-formatter

When NSDateFormatter is used to convert an NSDate type to a String type, you get to specify a custom format string. And some format specifiers are just 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 as warnings the following string patterns in a date format specifier:

  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. Here's the situation: when you pass Objective-C objects as parameters to the NSInvocation methods getArgument:atIndex: and getReturnValue:, the values get copied by value into the argument pointer. Which means incorrect releasing behavior if those object pointers aren't declared __unsafe_unretained. ๐Ÿ˜ญ

For code:

id arg;
[invocation getArgument:&arg atIndex:2];

__strong id returnValue;
[invocation getReturnValue:&returnValue];

The fix will be:

__unsafe_unretained id arg;
[invocation getArgument:&arg atIndex:2];

__unsafe_unretained id returnValue;
[invocation getReturnValue:&returnValue];

The check will warn on being passed instance variable references that have lifetimes other than __unsafe_unretained, but it does not propose a fix:

// "id _returnValue" is declaration of instance variable of class.
[invocation getReturnValue:&self->_returnValue];

objc-property-declaration

Finds property declarations in Objective-C files that do not follow the pattern of property names in Apple's programming guide. The property name should be in the format of Lower Camel Case. ๐Ÿช

For code:

@property(nonatomic, assign) int LowerCamelCase;

The fix will be:

@property(nonatomic, assign) int lowerCamelCase;

Okay but lowkey important detail: the check will only fix 'CamelCase' to 'camelCase'. In some other cases it'll only give you warning messages, because the property name could just be complicated. In those cases, users need to come up with a proper name on their own. Sorry bestie, this check can't name things for you. ๐Ÿซถ

This check also accepts special acronyms as prefixes or suffixes. Such prefixes or suffixes will suppress the Lower Camel Case check according to the guide: <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-1002931-BBCFHEAB>

For a full list of well-known acronyms: <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE>

The corresponding style rule: <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-1001757>

The check will also accept a property declared in a category if it's got a prefix of lowercase letters followed by a _ โ€” that's how you dodge naming conflicts ๐Ÿ’… for example:

@property(nonatomic, assign) int abc_lowerCamelCase;

And the style rule that this is all based on lives over here: https://developer.apple.com/library/content/qa/qa1908/_index.html ๐ŸŽ€

๐Ÿฆ‹ objc-super-self ๐Ÿฆ‹

okay so this one catches invocations of -self on super instances inside initializers of subclasses of NSObject, and it's like "bestieโ€ฆ call a superclass initializer instead??" ๐Ÿ˜ญ

Calling -self on super instances in initializers is a super common programmer oopsie โ€” the original intent was almost always to call a superclass initializer. And when you don't call a superclass initializer, you break initializer chaining, which can leave you with an invalid object initialization. no cap. ๐Ÿ’€

โœจ openmp-exception-escape โœจ

This one analyzes OpenMP Structured Blocks and checks that no exception escapes out of the Structured Block it was thrown in. ๐Ÿ‘‘

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 isn't caught in the same structured block it was thrown in, the behavior is undefined. ๐Ÿšจ

FIXME: this check does not model SEH, setjmp/longjmp.
WARNING! This check may be expensive on large source files. ๐Ÿ’ธ

Options

  • IgnoredExceptions โ€” a comma-separated list of type names that are not counted as thrown exceptions in the check. Default value is an empty string.

๐ŸŒธ openmp-use-default-none ๐ŸŒธ

Finds OpenMP directives that are allowed to contain a default clause, but either don't specify it at all, or specify it with a kind that isn't none โ€” and then suggests you use the default(none) clause. โœจ

Using the default(none) clause forces developers to explicitly spell out data sharing attributes for every variable referenced in the construct. which makes it obvious which variables are referenced and what their data sharing attribute is โ€” so readability goes up and errors get way easier to spot. it's giving "clarity, actually." ๐Ÿ’–

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 does two operations: it writes a newline character to the output stream and then flushes the stream buffer. Writing a single newline character with '\n' does not trigger a flush, which can improve performance. on top of that, flushing the stream buffer can cause extra overhead when you're working with streams that are buffered. ๐Ÿฆ‹

Example:

Consider this 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. โœจ

Also, important note bestie: 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. So using '\n' with these streams won't get you any performance gain, but it's still recommended to use '\n' for consistency and readability. ๐Ÿ’–

If you do actually 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. ๐ŸŽ€

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 โ€” used to ignore certain enum types. It accepts a semicolon-separated list of (fully qualified) enum type names or regular expressions that match the enum type names. The default value is an empty string, which means no enums will be ignored.

๐ŸŒˆ performance-faster-string-find ๐ŸŒˆ

Optimize calls to std::string::find() and friends when the needle passed in is a single character string literal. The character literal overload is more efficient. ๐Ÿ’…

Examples:

str.find("A");

// becomes

str.find('A');

Options

  • StringLikeClasses โ€” semicolon-separated list of names of string-like classes. By default only ::std::basic_string and ::std::basic_string_view are considered. The check will only consider member functions named find, rfind, find_first_of, find_first_not_of, find_last_of, or find_last_not_of within these classes.

๐Ÿงš performance-for-range-copy ๐Ÿงš

Finds C++11 for ranges where the loop variable is copied in each iteration, but it would totally suffice to obtain it by const reference. ๐Ÿ˜Œ

The check is only applied to loop variables of types that are expensive to copy โ€” which means they are not trivially copyable, or they have a non-trivial copy constructor or destructor.

To make sure it's actually 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 a 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 ๐Ÿ’ซ.

๐Ÿ“ performance-implicit-conversion-in-loop ๐Ÿ“

This warning shows up in a range-based loop with a loop variable of const ref type where the type of the variable doesn't match the one returned by the iterator. That means 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. โœจ

๐Ÿ’– 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 over 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

okay bestie so this one is likeโ€ฆ a vibe check on your string math, and your strings are failing it ๐Ÿ’€. It warns you about the performance overhead you get when you concatenate strings with operator+, like:

std::string a("Foo"), b("Bar");
a = a + b;

instead of that whole situation, you should be using operator+= or the append() member function of std::string's (std::basic_string) class. like this:

std::string a("Foo"), b("Baz");
for (int i = 0; i < 20000; ++i) {
    a = a + "Bar" + b;
}

which 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 bestie, this one 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, the check will only check the string usage in while, for and for-range statements. Default is false. ๐Ÿซถ

๐Ÿฆ‹ performance-inefficient-vector-operation

this one finds possible inefficient std::vector operations (think push_back, emplace_back) that may cause unnecessary memory reallocations. literally the worst.

it can also find calls that add an element to a protobuf repeated field in a loop without calling Reserve() before the loop. calling Reserve() first can avoid those unnecessary memory reallocations. no cap.

currently, the check only detects following kinds of loops with a single statement body:

  • counter-based for loops start with 0:
std::vector<int> v;
for (int i = 0; i < n; ++i) {
  v.push_back(n);
  // This will trigger the warning since the push_back may cause multiple
  // memory reallocations in v. This can be avoid by inserting a 'reserve(n)'
  // statement before the for statement.
}

SomeProto p;
for (int i = 0; i < n; ++i) {
  p.add_xxx(n);
  // This will trigger the warning since the add_xxx may cause multiple memory
  // reallocations. This can be avoid by inserting a
  // 'p.mutable_xxx().Reserve(n)' statement before the for statement.
}
  • for-range loops like for (range-declaration : range_expression), the type 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. ๐Ÿ’…
  • EnableProto โ€” when true, the check will also warn on inefficient operations for proto repeated fields. otherwise, the check only warns on inefficient vector operations. Default is false. ๐ŸŒธ

๐Ÿงš performance-move-const-arg

bestie this check warns:

  • if std::move() is called with a constant argument,
  • if std::move() is called with an argument of a trivially-copyable type,
  • if the result of std::move() is passed as a const reference argument.

in all three cases, the check will suggest a fix that removes the std::move(). iconic behaviour honestly.

here are examples of each of the three cases:

const string s;
return std::move(s);  // Warning: std::move of the const variable has no effect

int x;
return std::move(x);  // Warning: std::move of the variable of a trivially-copyable type has no effect

void f(const string &s);
string s;
f(std::move(s));  // Warning: passing result of std::move as a const reference argument; no move will actually happen

โœจ Options โœจ

  • CheckTriviallyCopyableMove โ€” if true, enables detection of trivially copyable types that do not have a move constructor. Default 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 ๐Ÿ’…

the check flags user-defined move constructors that have a ctor-initializer initializing a member or base class through a copy constructor instead of a move constructor. not the move, bestie. ๐Ÿ˜ญ


๐Ÿ‘‘ performance-no-automatic-move

finds local variables that cannot be automatically moved due to constness.

under certain conditions https://en.cppreference.com/w/cpp/language/return#automatic_move_from_local_variables_and_parameters, local values are automatically moved out when returning from a function. a common mistake is to declare local lvalue variables const, which prevents the move. ๐Ÿ™ˆ

Example [1] https://godbolt.org/z/x7SYYA:

StatusOr<std::vector<int>> Cool() {
  std::vector<int> obj = ...;
  return obj;  // calls StatusOr::StatusOr(std::vector<int>&&)
}

StatusOr<std::vector<int>> NotCool() {
  const std::vector<int> obj = ...;
  return obj;  // calls `StatusOr::StatusOr(const std::vector<int>&)`
}

the former version (Cool) should be preferred over the latter (NotCool) as it will avoid allocations and potentially large memory copies. slay.

๐Ÿซถ Semantics

in the example above, StatusOr::StatusOr(T&&) have the same semantics as long as the copy and move constructors for T have the same semantics. note that there is no guarantee that S::S(T&&) and S::S(const T&) have the same semantics for any single S, so we're not providing automated fixes for this check, and judgement should be exerted when making the suggested changes. ๐Ÿง 

๐Ÿ’œ -Wreturn-std-move

another case where the move cannot happen is the following:

StatusOr<std::vector<int>> Uncool() {
  std::vector<int>&& obj = ...;
  return obj;  // calls `StatusOr::StatusOr(const std::vector<int>&)`
}

in that case the fix is more consensual: just return std::move(obj). this is handled by the -Wreturn-std-move warning. periodt.


โญ performance-no-int-to-ptr

diagnoses every integer to pointer cast. every single one bestie, it's thorough.

while casting an (integral) pointer to an integer is obvious โ€” you just get the integral value of the pointer โ€” casting an integer to an (integral) pointer is deceivingly different. while you will get a pointer with that integral value, if you got that integral value via a pointer-to-integer cast originally, the new pointer will lack the provenance information from the original pointer. ๐Ÿ’€

so while (integral) pointer to integer casts are effectively no-ops, and are transparent to the optimizer, integer to (integral) pointer casts are NOT transparent, and may conceal information from optimizer.

while that may be the intention, it is not always so. for example, let's take a look at a routine to align the pointer up to the multiple of 16: the obvious, naive implementation for that is:

char* src(char* maybe_underbiased_ptr) {
  uintptr_t maybe_underbiased_intptr = (uintptr_t)maybe_underbiased_ptr;
  uintptr_t aligned_biased_intptr = maybe_underbiased_intptr + 15;
  uintptr_t aligned_intptr = aligned_biased_intptr & (~15);
  return (char*)aligned_intptr; // warning: avoid integer to pointer casts [performance-no-int-to-ptr]
}

the check will rightfully diagnose that cast. and like, fair.

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

the check flags user-defined destructors marked with noexcept(expr) where expr evaluates to false (but is not a false literal itself).

when a destructor is marked as noexcept, it assures the compiler that no exceptions will be thrown during the destruction of an object, which allows the compiler to perform certain optimizations such as omitting exception handling code. the way that noexcept unlocks free real estate in your binaryโ€ฆ obsessed. โœจ


๐Ÿ“ performance-noexcept-move-constructor

the check flags user-defined move constructors and assignment operators not marked with noexcept or 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 same is valid for move assignment operations. don't let your moves get copied bestie ๐Ÿ˜ญ


๐Ÿ’… performance-noexcept-swap

the check 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. swap in peace ๐Ÿ•Š๏ธ


๐ŸŒธ performance-trivially-destructible

finds types that could be made trivially-destructible by removing out-of-line defaulted destructor declarations. like, why are you even doing that.

struct A: TrivialType {
  ~A(); // Makes A non-trivially-destructible.
  TrivialType trivial_fields;
};
A::~A() = default;

๐Ÿฆ‹ performance-type-promotion-in-math-fn

finds calls to C math library functions (from math.h or, in C++, cmath) with implicit float to double promotions. sneaky little promotions. ๐Ÿ™ˆ

for example, warns on ::sin(0.f), because this function's parameter is a double. you probably meant to call std::sin(0.f) (in C++), or sinf(0.f) (in C).

float a;
asin(a);

// becomes

float a;
std::asin(a);

๐ŸŽ€ performance-unnecessary-copy-initialization

finds local variable declarations that are initialized using the copy constructor of a non-trivially-copyable type but it would suffice to obtain a const reference. bestie that copy was never needed. ๐Ÿ’€

the check is only applied if it is safe to replace the copy by a const reference. this is the case when the variable is const qualified or when it is only used as a const, i.e. only const methods or operators are invoked on it, or it is used as const reference or value argument in constructors or function calls. ๐Ÿ‘‘

Example:

          const string& constReference();
          void Function() {
            // The warning will suggest making this a const reference.
            const string UnnecessaryCopy = constReference();
          }

          struct Foo {
            const string& name() const;
          };
          void Function(const Foo& foo) {
            // The warning will suggest making this a const reference.
            string UnnecessaryCopy1 = foo.name();
            UnnecessaryCopy1.find("bar");

            // The warning will suggest making this a const reference.
            string UnnecessaryCopy2 = UnnecessaryCopy1;
            UnnecessaryCopy2.find("bar");
          }

โœจ Options โœจ

  • AllowedTypes โ€” a semicolon-separated list of type names that are allowed to be initialized by copying, bestie. Regular expressions are accepted here, so like [Rr]ef(erence)?$ matches every type with the suffix Ref, ref, Reference and reference. Default is empty. Oh and heads up: if a name in the list contains the sequence ::, it gets matched against the qualified type name (i.e. namespace::Type), otherwise it's matched against just the type name (i.e. Type).
  • ExcludedContainerTypes โ€” a semicolon-separated list of type names whose methods are allowed to return the const reference that the variable gets copied from. When an expensive-to-copy variable is copy-initialized from the return value of a method on a type in this list, the check does not fire. Use this to exclude types that are known to be const-incorrect, or where the lifetime/immutability of the returned reference isn't tied to mutations of the container. A prime example: view types that don't own the underlying data. Same deal as AllowedTypes above โ€” regular expressions accepted, and whether :: is present decides if the qualified typename gets matched or not.

๐Ÿ’€ Limitations ๐Ÿ’€

This check does not do lifetime analysis, and it may absolutely suggest swapping copies for const references that end up dangling. So be careful when the referenced object could get invalidated by stuff that happens afterwards. Not me warning you for free, fr fr:

          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 ๐ŸŒธ

Flags value parameter declarations of expensive-to-copy types that get copied on every single invocation, when honestly it would've been fine to pass them by const reference. The way that it's such an easy win.

Quick vibe check on where this check even looks: it's only applied to parameters of types that are expensive to copy, which means they're not trivially copyable, or they have a non-trivial copy constructor or destructor.

To make sure replacing the value parameter with a const reference is actually safe, it uses this little heuristic:

  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, because obviously:

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

And if the parameter is not const, only gets copied or assigned once, and has a non-trivial move-constructor or move-assignment operator respectively, then the check will suggest you move it instead. Iconic behaviour.

Example:

          void setValue(string Value) {
            Field = Value;
          }

Will become:

          #include <utility>

          void setValue(string Value) {
            Field = std::move(Value);
          }
Because the fix-it needs to change the signature of the function, it may break builds if the function is used in multiple translation units, or if some code depends on function signatures. Heads up bestie, that's the risk. ๐Ÿ˜ญ

โœจ Options โœจ

  • IncludeStyle โ€” a string specifying which include-style is used, llvm or google. Default is llvm.
  • AllowedTypes โ€” a semicolon-separated list of type names 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 is 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 saying 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 info. Default is true.

๐ŸŽ€ portability-avoid-pragma-once ๐ŸŽ€

Finds uses of #pragma once and suggests replacing them with standard include guards (#ifndef/#define/#endif) for improved portability. Slay, honestly.

#pragma once is a non-standard extension, despite being widely supported by modern compilers. Relying on it can lead to portability issues in some environments. ๐Ÿ˜ฌ

Some older or specialized C/C++ compilers โ€” particularly in embedded systems โ€” may not fully support #pragma once.

It can also fail in certain file system configurations, like network drives or complex symbolic links, potentially leading to compilation issues. Lowkey terrifying.

Consider the following header file:

          // my_header.h
          #pragma once // warning: avoid 'pragma once' directive; use include guards instead

The warning suggests using include guards:

          // my_header.h
          #ifndef PATH_TO_MY_HEADER_H // Good: use include guards.
          #define PATH_TO_MY_HEADER_H

          #endif // PATH_TO_MY_HEADER_H

๐Ÿฆ‹ portability-restrict-system-includes ๐Ÿฆ‹

Checks to selectively allow or disallow a configurable list of system headers. Your include list, your rules. ๐Ÿ‘‘

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 <https://wg21.link/p0214>) alternatives. Glow up your SIMD, periodt.

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. Chef's kiss. ๐Ÿ’…

Otherwise, it just complains that the intrinsics are non-portable (and that there are P0214 <https://wg21.link/p0214> alternatives).

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 <https://wg21.link/p0214> and its extensions cover many common SIMD operations. By migrating from target-dependent intrinsics to P0214 <https://wg21.link/p0214> operations, the SIMD code can be simplified and pieces for different targets can be unified. So satisfying.

Refer to P0214 <https://wg21.link/p0214> for introduction and motivation for the data-parallel standard library.

โœจ Options โœจ

  • Suggest โ€” if this option is set to true (default is false), the check will suggest P0214 <https://wg21.link/p0214> alternatives, otherwise it only points out that the intrinsic function is non-portable.
  • Std โ€” the namespace used to suggest P0214 <https://wg21.link/p0214> alternatives. If not specified, std:: for -std=c++20 and std::experimental:: for -std=c++11.

๐Ÿ’œ portability-std-allocator-const ๐Ÿ’œ

Report use of std::vector<const T> (and similar containers of const elements). These are not allowed in standard C++, and should usually be std::vector<T> instead."

Per C++ [allocator.requirements.general]: "T is any cv-unqualified object type", std::allocator<const T> is undefined. Many standard containers use std::allocator by default and therefore their const T instantiations are undefined.

libc++ defines std::allocator<const T> as an extension which will be removed in the future. ๐Ÿ™ˆ

libstdc++ and MSVC do not support std::allocator<const T>:

          // libstdc++ has a better diagnostic since https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48101
          std::deque<const int> deque; // error: static assertion failed: std::deque must have a non-const, non-volatile value_type
          std::set<const int> set; // error: static assertion failed: std::set must have a non-const, non-volatile value_type
          std::vector<int* const> vector; // error: static assertion failed: std::vector must have a non-const, non-volatile value_type

          // MSVC
          // error C2338: static_assert failed: 'The C++ Standard forbids containers of const elements because allocator<const T> is ill-formed.'

Code bases only compiled with libc++ may accrue such undefined usage. This check finds such code and prevents backsliding while clean-up is ongoing. We love a check that holds you accountable. ๐Ÿซถ


โญ portability-template-virtual-member-function โญ

Finds cases when an uninstantiated virtual member function in a template class causes cross-compiler incompatibility. That's it, that's the check โ€” no notes, just portability problems getting caught in 4K.

okay so bestie, when you instantiate a template class, the non-virtual member functions don't have to be instantiated unless they're actually used. cute, right? But virtual member function instantiation? That's unspecified, and it's fully up to however the compiler feels like implementing it that day. ๐Ÿ™ˆ

In the snippets below, the virtual member function is not instantiated by GCC and Clang, but it is instantiated by MSVC โ€” so the snippet gets accepted by the first two and rejected by MSVC. the way that happens is so real ๐Ÿ˜ญ

template<typename T>
struct CrossPlatformError {
    virtual ~CrossPlatformError() = default;

    static void used() {}

    virtual void unused() {
        T MSVCError = this;
    };
};

int main() {
    CrossPlatformError<int>::used();
    return 0;
}

So cross-platform projects that need to support MSVC on Windows might see compiler errors, because certain virtual member functions get instantiated that other compilers on other platforms just... don't bother with. This check highlights those virtual member functions for you. ๐Ÿ’…


โœจ readability-ambiguous-smartptr-reset-call โœจ

Finds potentially erroneous calls to the reset method on smart pointers when the pointee type also has a reset method. Having a reset method in both classes makes it super easy to accidentally null the pointer when what you actually wanted was to reset the underlying object. it's giving "one tiny character, one big bug" ๐Ÿ’€

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, bestie, but the second one might not be what the developer intended, because it destroys the pointed-to object instead of resetting its state. And it's so easy to make such a typo since the difference between . and -> is literally tiny. ๐Ÿ™ˆ

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 <#cmdoption-arg-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 <#cmdoption-arg-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. periodt.

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. like why are you even doing that bestie, it changes nothing ๐ŸŒธ

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, also known as ternary operators, can contribute to reduced code readability and comprehension. So they should be split into several statements and the intermediate results should be stored in a temporary variable. your future self reading this at 2am 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. lowkey misleading, ngl ๐Ÿคจ

Example:

void g();
void f() {
    // ...
    return g();
}

In a long function body, the return statement suggests that the function returns a value. However, return g(); is a combination of two statements that should be written as

g();
return;

to make it clear that g() is called and immediately afterwards the function returns (nothing). iconic clarity behavior โœจ

In C, the same issue is detected by the compiler if the -Wpedantic mode is enabled.

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, such as #if 0 and #if 1, etc. ๐Ÿ‘€

#if 0
    // some disabled code
#endif

#if 1
   // some enabled code that can be disabled manually
#endif

Unconditional preprocessor directives โ€” like #if 0 for disabled code and #if 1 for enabled code โ€” can lead to dead code and always-enabled code, respectively. Dead code can make understanding the codebase more difficult, hinder readability, and may be a sign of unfinished functionality or abandoned features. This can cause maintenance issues, confusion for future developers, and potential compilation problems. it's giving โœจchaosโœจ

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


โœจ 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. bestie, just use the braces, it costs you nothing ๐ŸŽ€

Before:

if (condition)
  statement;

After:

if (condition) {
  statement;
}

Options

  • ShortStatementLines โ€” Defines the minimal number of lines that the statement should have in order to trigger this check.

The number of lines is counted from the end of condition or initial keyword (do/else) until the last line of the inner statement. Default value 0 means that braces will be added to all statements (not having them already).


โœจ readability-const-return-type โœจ

Checks for functions with a const-qualified return type and recommends removal of the const keyword. Such use of const is usually superfluous, and can prevent valuable compiler optimizations. Does not (yet) fix trailing return types. ๐Ÿ™ˆ

Examples:

const int foo();
const Clazz foo();
Clazz *const foo();

Note that this applies strictly to top-level qualification, which excludes pointers or references to const values. For example, these are fine:

const int* foo();
const int& foo();
const Clazz* foo();

Options

  • IgnoreMacros โ€” If set 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. slay, efficiency ๐Ÿ’…

Examples โ€” so like, here's the lil cheat sheet of what becomes what ๐Ÿ’ซ

  • myMap.find(x) == myMap.end() โ†’ becomes !myMap.contains(x)
  • myMap.find(x) != myMap.end() โ†’ becomes myMap.contains(x)
  • myStr.find(x) != std::string::npos โ†’ becomes myStr.contains(x)
  • if (myMap.count(x)) โ†’ becomes if (myMap.contains(x))
  • bool exists = myMap.count(x) โ†’ becomes bool exists = myMap.contains(x)
  • bool exists = myMap.count(x) > 0 โ†’ becomes bool exists = myMap.contains(x)
  • bool exists = myMap.count(x) >= 1 โ†’ becomes bool exists = myMap.contains(x)
  • bool missing = myMap.count(x) == 0 โ†’ becomes 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. bestie it's literally everywhere now ๐Ÿ’…


readability-container-data-pointer ๐ŸŒˆ

Finds cases where code could use data() rather than the address of the element at index 0 in a container. This pattern is commonly used to materialize a pointer to the backing data of a container. std::vector and std::string provide a data() accessor to retrieve the data pointer which should be preferred. iconic behavior tbh

This also ensures that in the case that the container is empty, the data pointer access does not perform an errant memory access. ๐Ÿšจ like, we are NOT doing that today.

โœจ Options โœจ

  • IgnoredContainers โ€” Semicolon-separated list of containers regexp for which this check won't be enforced. Default is an empty string.

readability-container-size-empty ๐ŸŽ€

Checks whether a call to the size()/length() method can be replaced with a call to empty(). ๐Ÿ‘‘

The emptiness of a container should be checked using the empty() method instead of the size()/length() method. It shows clearer intent to use empty(). Furthermore some containers (for example, a std::forward_list) may implement the empty() method but not implement the size() or length() method. Using empty() whenever possible makes it easier to switch to another container in the future. future-proofing queen fr fr ๐Ÿ’œ

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. no cap, any of them ๐Ÿฆ‹


โœจ 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. lowkey humbling for those functions ngl ๐Ÿซถ

After applying modifications as suggested by the check, running the check again might find more opportunities to mark member functions static. it's giving onion, layers, you gotta keep peeling ๐Ÿง…

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.

int *p;
if (p)
  delete p;

like bestie... the if was doing absolutely nothing. it ate zero and left no crumbs ๐Ÿ“


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

see how it respects the macro drama? the check said "not today, the state changed" ๐Ÿ’…

โœจ 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 ๐ŸŒธ

LLVM Coding Standards (https://llvm.org/docs/CodingStandards.html) advises to reduce indentation where possible and where it makes understanding code easier. Early exit is one of the suggested enforcements of that. Please do not use else or else if after something that interrupts control flow โ€” like return, break, continue, throw. ๐Ÿšช the door is RIGHT there, just leave.

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

de-nested, de-cluttered, glowing โœจ

โœจ 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. spicy defaults, honestly

This check helps to enforce this LLVM Coding Standards recommendation (https://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return).


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. sneaky implicit numbers, the audacity ๐Ÿ˜ญ

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 {    // Invalid, d1 is not explicitly initialized!
  d0 = 0,
  d1,
  d2 = 2,
};

enum E {    // Invalid, e1, e3, and e5 are not explicitly initialized.
  e0 = 0,
  e1,
  e2 = 2,
  e3,       // Dangerous, as the numeric values of e3 and e5 are both 3, and this is not explicitly visible in the code!
  e4 = 2,
  e5,
};

Dangerous is doing a lot of heavy lifting in that enum E comment and it is CORRECT to be scared. ๐Ÿ’€

This check corresponds to the CERT C Coding Standard recommendation INT09-C. Ensure enumeration constants map to unique values (https://wiki.sei.cmu.edu/confluence/display/c/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 โ€” If set to false, explicit initialization to sequential values are not allowed. Default is true.
enum G {
  g0 = 1, // Not allowed if AllowExplicitSequentialInitialValues is false.
  g1 = 2,
  g2 = 3,

readability-function-cognitive-complexity ๐Ÿ’œ

Checks function Cognitive Complexity metric. Yes bestie, we're measuring vibes-per-function but mathematically.

The metric is implemented as per the COGNITIVE COMPLEXITY by SonarSource <https://www.sonarsource.com/docs/CognitiveComplexity.pdf> specification version 1.2 (19 April 2017). That's the receipts, that's the source of truth, no cap.


โœจ Options โœจ

  • Threshold โ€” Flag functions with Cognitive Complexity exceeding this number. The default is 25. So if your function's brain is doing more than 25 thoughts at onceโ€ฆ flagged, sorry.
  • DescribeBasicIncrements โ€” If set to true, then 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 literally points at the culprit and goes "this one, right here ๐Ÿ’…".
  • IgnoreMacros โ€” If set to true, the check will ignore code inside macros. Note, that 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! A little trio. The Holy Trinity of "why is this function like this".

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 ๐Ÿช†

While by itself the nesting level does not change the function's Cognitive Complexity metric, it is tracked, and is used by the next, third building block. 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. 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.

void function0() {}

Slightly better example. This function has Cognitive Complexity of 1.

int function1(bool var) {
  if(var) // +1, nesting level +1
    return 42;
  return 0;
}

Full example. This function has Cognitive Complexity of 3.

int function3(bool var1, bool var2) {
  if(var1) { // +1, nesting level +1
    if(var2)  // +2 (1 + current nesting level of 1), nesting level +1
      return 42;
  }

  return 0;
}

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


โš ๏ธ Limitations โš ๏ธ

The metric is implemented with two notable exceptions:

  • preprocessor conditionals (#ifdef, #if, #elif, #else, #endif) are not accounted for.
  • each method in a recursion cycle is not accounted for. It can't be fully implemented, because cross-translational-unit analysis would be needed, which is currently not possible in clang-tidy.
So likeโ€ฆ the number is almost the whole truth but not the whole truth, bestie. Know that before you start beefing with your coworkers over a score.

readability-function-size ๐Ÿฆ‹

google-readability-function-size redirects here as an alias for this check.

Checks for large functions based on various metrics. It's giving "girl, this function is a novel".


โœจ 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.
  • CountMemberInitAsStmt โ€” When true, count class member initializers in constructors as statements. Default is true.

readability-identifier-length ๐ŸŽ€

This check finds variables and function parameters whose length are too short. The desired name length is configurable.

Special cases are supported for loop counters and for exception variable names. Because obviously we're not gonna bully you for writing i. That would be unhinged.


โœจ Options โœจ

The following options are described below:

  • MinimumVariableNameLength, IgnoredVariableNames
  • MinimumParameterNameLength, IgnoredParameterNames
  • MinimumLoopCounterNameLength, IgnoredLoopCounterNames
  • MinimumExceptionNameLength, IgnoredExceptionVariableNames

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

This check does not have any fix suggestions in the general case since variable names have semantic value.

IgnoredVariableNames

Specifies a regular expression for variable names that are to be ignored. The default value is empty, thus no names are ignored.

MinimumParameterNameLength

All function parameter names are expected to have a length of at least MinimumParameterNameLength (default is 3). Setting it to 0 or 1 disables the check entirely.

int doubler(int x)   // warns that x is too short
{
   return 2 * x;
}

This check does not have any fix suggestions in the general case since variable names have semantic value.

IgnoredParameterNames

Specifies a regular expression for parameters that are to be ignored. The default value is ^[n]$ for historical reasons.

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

readability-identifier-naming ๐Ÿ‘‘

Checks for identifiers naming style mismatch.

This check will try to enforce coding guidelines on the identifiers naming. It supports one of the following casing types and tries to convert from one to another if a mismatch is detected.

Casing types include:

  • 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 will be prepended or appended to the identifiers, regardless of the casing.

Many configuration options are available, in order to be able to create different rules for different kinds of identifiers. In general, the rules are falling back to a more generic rule if the specific case is not configured.

The naming of virtual methods is reported where they occur in the base class, but not where they are overridden, as it can't be fixed locally there. This also applies for pseudo-override patterns like CRTP.

Leading_upper_snake_case is a naming convention where the first word is capitalized followed by lower case word(s) separated by underscore(s) '_'. Examples include: Cap_snake_case, Cobra_case, Foo_bar_baz, and Master_copy_8gb.

Hungarian notation can be customized using different HungarianPrefix settings. The options and their corresponding values are:

  • Off - the default setting
  • On - example: int iVariable
  • LowerCase - example: int i_Variable
  • CamelCase - example: int IVariable

okay bestie so here's the tea โ˜• โ€” this check only comes for the identifier kinds you've actually configured. Which means an empty config effectively disables it โ€” the check just vibes in the corner doing nothing, no cap ๐Ÿ’€. But the DefaultCase option exists so you can enforce style on all kinds of identifiers at once, and then optionally override specific kinds that you want in a different case. Flexible queen behavior, honestly ๐Ÿ‘‘

For example, using values of:

  • DefaultCase of lower_case
  • MacroDefinitionCase of UPPER_CASE
  • TemplateParameterCase of CamelCase

It identifies and transforms names like this, fr fr:

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

the way that macroDefinition walked in and MACRO_DEFINITION strutted out ๐Ÿ’…


โœจ Options summary โœจ

The available options are summarized below, bestie โ€” it's a whole menu, so buckle up ๐ŸŽ€:

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

not the alphabetical-adjacent list going on for a whole screen ๐Ÿ˜ญ it's giving โœจorganized chaosโœจ


๐ŸŒธ Options description ๐ŸŒธ

A detailed description of each option is presented below, bestie โ€” read it, it's good for you ๐Ÿซถ:

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 defined, the check will ensure abstract class names will add the suffix with the given value (regardless of casing).

AbstractClassHungarianPrefix โ€” 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:

  • AbstractClassCase of lower_case
  • AbstractClassPrefix of pre_
  • AbstractClassSuffix of _post
  • AbstractClassHungarianPrefix of On

Identifies and/or transforms abstract class names as follows:

Before:

class ABSTRACT_CLASS {
public:
  ABSTRACT_CLASS();
};

After:

class pre_abstract_class_post {
public:
  pre_abstract_class_post();
};

she got a glow-up, a prefix AND a suffix, and she ate ๐Ÿ’…โœจ

AggressiveDependentMemberLookup โ€” When set to true the check will look in dependent base classes for dependent member references that need changing. Heads up though: this can lead to errors with template specializations, so the default value is false. Don't just flip it on for funsies, okay? ๐Ÿ™ˆ

For example using values of:

  • 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;
  }
};
  • CheckAnonFieldInParent โ€” set this to true and fields living inside anonymous records (aka anonymous unions and structs) get treated as names in the enclosing scope instead of public members of the anonymous record, at least as far as name checking is concerned. ๐Ÿ’…

For example:

class Foo {
private:
  union {
    int iv_;
    float fv_;
  };
};

If CheckAnonFieldInParent is false, you may get warnings that iv_ and fv_ are not coherent to public member names, because iv_ and fv_ are public members of the anonymous union. When CheckAnonFieldInParent is true, iv_ and fv_ will be treated as private data members of Foo for the purpose of name checking and thus no warnings will be emitted. no warnings, no stress, we love that for us โœจ

  • ClassCase โ€” when defined, the check makes sure class names conform to the selected casing.
  • ClassPrefix โ€” when defined, the check makes sure class names will add the prefix with the given value (regardless of casing).
  • ClassIgnoredRegexp โ€” identifier naming checks won't be enforced for class names matching this regular expression.
  • ClassSuffix โ€” when defined, the check makes sure class names will add the suffix with the given value (regardless of casing).
  • ClassHungarianPrefix โ€” 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:

  • ClassCase of lower_case
  • ClassPrefix of pre_
  • ClassSuffix of _post
  • ClassHungarianPrefix of On

Identifies and/or transforms class names as follows:

Before:

class FOO {
public:
  FOO();
  ~FOO();
};

After:

class pre_foo_post {
public:
  pre_foo_post();
  ~pre_foo_post();
};
  • 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 will add the prefix with the given value (regardless of casing).
  • ClassConstexprIgnoredRegexp โ€” identifier naming checks won't be enforced for class constexpr names matching this regular expression.
  • ClassConstexprSuffix โ€” when defined, the check makes sure class constexpr names will add the suffix with the given value (regardless of casing).
  • ClassConstexprHungarianPrefix โ€” 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:

  • ClassConstexprCase of lower_case
  • ClassConstexprPrefix of pre_
  • ClassConstexprSuffix of _post
  • ClassConstexprHungarianPrefix of On

Identifies and/or transforms class constexpr variable names as follows:

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 will add the prefix with the given value (regardless of casing).
  • ClassConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for class constant names matching this regular expression.
  • ClassConstantSuffix โ€” when defined, the check makes sure class constant names will add the suffix with the given value (regardless of casing).
  • ClassConstantHungarianPrefix โ€” 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:

  • ClassConstantCase of lower_case
  • ClassConstantPrefix of pre_
  • ClassConstantSuffix of _post
  • ClassConstantHungarianPrefix of On

Identifies and/or transforms class constant names as follows:

Before:

class FOO {
public:
  static const int CLASS_CONSTANT;
};

After:

class FOO {
public:
  static const int pre_class_constant_post;
};
  • ClassMemberCase โ€” when defined, the check makes sure class member names conform to the selected casing.
  • ClassMemberPrefix โ€” when defined, the check makes sure class member names will add the prefix with the given value (regardless of casing).
  • ClassMemberIgnoredRegexp โ€” identifier naming checks won't be enforced for class member names matching this regular expression.
  • ClassMemberSuffix โ€” when defined, the check makes sure class member names will add the suffix with the given value (regardless of casing).
  • ClassMemberHungarianPrefix โ€” 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:

  • ClassMemberCase of lower_case
  • ClassMemberPrefix of pre_
  • ClassMemberSuffix of _post
  • ClassMemberHungarianPrefix of On

Identifies and/or transforms class member names as follows:

Before:

class FOO {
public:
  static int CLASS_CONSTANT;
};

After:

class FOO {
public:
  static int pre_class_constant_post;
};
  • ClassMethodCase โ€” when defined, the check makes sure class method names conform to the selected casing.
  • ClassMethodPrefix โ€” when defined, the check makes sure class method names will add the prefix with the given value (regardless of casing).
  • ClassMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for class method names matching this regular expression.
  • ClassMethodSuffix โ€” when defined, the check makes sure class method names will add the suffix with the given value (regardless of casing).

For example using values of:

  • ClassMethodCase of lower_case
  • ClassMethodPrefix of pre_
  • ClassMethodSuffix of _post

Identifies and/or transforms class method names as follows:

Before:

class FOO {
public:
  int CLASS_MEMBER();
};

After:

class FOO {
public:
  int pre_class_member_post();
};
  • ConceptCase โ€” when defined, the check makes sure concept names conform to the selected casing.
  • ConceptPrefix โ€” when defined, the check makes sure concept names will add the prefix with the given value (regardless of casing).
  • ConceptIgnoredRegexp โ€” identifier naming checks won't be enforced for concept names matching this regular expression.
  • ConceptSuffix โ€” when defined, the check makes sure concept names will add the suffix with the given value (regardless of casing).

For example using values of:

  • ConceptCase of CamelCase
  • ConceptPrefix of Pre
  • ConceptSuffix of Post

Identifies and/or transforms concept names as follows:

Before:

template<typename T> concept my_concept = requires (T t) { {t++}; };

After:

template<typename T> concept PreMyConceptPost = requires (T t) { {t++}; };
  • ConstantCase โ€” when defined, the check makes sure constant names conform to the selected casing.
  • ConstantPrefix โ€” when defined, the check makes sure constant names will add the prefix with the given value (regardless of casing).
  • ConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for constant names matching this regular expression.
  • ConstantSuffix โ€” when defined, the check makes sure constant names will add the suffix with the given value (regardless of casing).
  • ConstantHungarianPrefix โ€” 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:

  • ConstantCase of lower_case
  • ConstantPrefix of pre_
  • ConstantSuffix of _post
  • ConstantHungarianPrefix of On

Identifies and/or transforms constant names as follows:

Before:

void function() { unsigned const MyConst_array[] = {1, 2, 3}; }

After:

void function() { unsigned const pre_myconst_array_post[] = {1, 2, 3}; }
  • ConstantMemberCase โ€” when defined, the check makes sure constant member names conform to the selected casing.
  • ConstantMemberPrefix โ€” when defined, the check makes sure constant member names will add the prefix with the given value (regardless of casing).
  • ConstantMemberIgnoredRegexp โ€” identifier naming checks won't be enforced for constant member names matching this regular expression.
  • ConstantMemberSuffix โ€” when defined, the check makes sure constant member names will add the suffix with the given value (regardless of casing).
  • ConstantMemberHungarianPrefix โ€” 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:

  • ConstantMemberCase of lower_case
  • ConstantMemberPrefix of pre_
  • ConstantMemberSuffix of _post
  • ConstantMemberHungarianPrefix of On

Identifies and/or transforms constant member names as follows:

Before:

class Foo {
  char const MY_ConstMember_string[4] = "123";
}

After:

class Foo {
  char const pre_my_constmember_string_post[4] = "123";
}
  • ConstantParameterCase โ€” when defined, the check makes sure constant parameter names conform to the selected casing.
  • ConstantParameterPrefix โ€” when defined, the check makes sure constant parameter names will add the prefix with the given value (regardless of casing).
  • ConstantParameterIgnoredRegexp โ€” if a constant parameter's name matches this regex, the naming checks simply will not be enforced on it. It's exempt, it's got a hall pass, we're leaving it alone. ๐Ÿ™ˆ
  • ConstantParameterSuffix โ€” when you define this, the check makes sure constant parameter names get the suffix you give it tacked on the end (regardless of casing, so don't stress about that).
  • ConstantParameterHungarianPrefix โ€” turn this on and the check makes sure the declared identifier carries a Hungarian notation prefix based on whatever type it was declared as. โœจ

Okay so for example, using values of:

  • ConstantParameterCase of lower_case
  • ConstantParameterPrefix of pre_
  • ConstantParameterSuffix of _post
  • ConstantParameterHungarianPrefix of On

The check identifies and/or transforms constant parameter names like this:

Before:

void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);

After:

void GLOBAL_FUNCTION(int PARAMETER_1, int const pre_const_parameter_post);

  • ConstantPointerParameterCase โ€” when defined, the check makes sure constant pointer parameter names conform to whichever casing you picked. ๐Ÿ’…
  • ConstantPointerParameterPrefix โ€” when defined, the check makes sure constant pointer parameter names get the prefix you give it added on (regardless of casing).
  • ConstantPointerParameterIgnoredRegexp โ€” if a constant pointer parameter's name matches this regex, the identifier naming checks won't be enforced on it. Not today, not ever.
  • ConstantPointerParameterSuffix โ€” when defined, the check makes sure constant pointer parameter names get the suffix you give it added on (regardless of casing).
  • ConstantPointerParameterHungarianPrefix โ€” when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐ŸŽ€

For example using values of:

  • ConstantPointerParameterCase of lower_case
  • ConstantPointerParameterPrefix of pre_
  • ConstantPointerParameterSuffix of _post
  • ConstantPointerParameterHungarianPrefix of On

It identifies and/or transforms constant pointer parameter names as follows:

Before:

void GLOBAL_FUNCTION(int const *CONST_parameter);

After:

void GLOBAL_FUNCTION(int const *pre_const_parameter_post);

  • ConstexprFunctionCase โ€” when defined, the check makes sure constexpr function names conform to the selected casing. โญ
  • ConstexprFunctionPrefix โ€” when defined, the check makes sure constexpr function names get the prefix with the given value added on (regardless of casing).
  • ConstexprFunctionIgnoredRegexp โ€” identifier naming checks won't be enforced for constexpr function names matching this regular expression. Skrrrt, skipped. ๐Ÿ™ˆ
  • ConstexprFunctionSuffix โ€” when defined, the check makes sure constexpr function names get the suffix with the given value added on (regardless of casing).

For example using values of:

  • ConstexprFunctionCase of lower_case
  • ConstexprFunctionPrefix of pre_
  • ConstexprFunctionSuffix of _post

It identifies and/or transforms constexpr function names as follows:

Before:

constexpr int CE_function() { return 3; }

After:

constexpr int pre_ce_function_post() { return 3; }

  • ConstexprMethodCase โ€” when defined, the check makes sure constexpr method names conform to the selected casing. ๐ŸŒธ
  • ConstexprMethodPrefix โ€” when defined, the check makes sure constexpr method names get the prefix with the given value added on (regardless of casing).
  • ConstexprMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for constexpr method names matching this regular expression. She's exempt, periodt.
  • ConstexprMethodSuffix โ€” when defined, the check makes sure constexpr method names get the suffix with the given value added on (regardless of casing).

For example using values of:

  • ConstexprMethodCase of lower_case
  • ConstexprMethodPrefix of pre_
  • ConstexprMethodSuffix of _post

It identifies and/or transforms constexpr method names as follows:

Before:

class Foo {
public:
  constexpr int CST_expr_Method() { return 2; }
}

After:

class Foo {
public:
  constexpr int pre_cst_expr_method_post() { return 2; }
}

  • ConstexprVariableCase โ€” when defined, the check makes sure constexpr variable names conform to the selected casing. โœจ
  • ConstexprVariablePrefix โ€” when defined, the check makes sure constexpr variable names get the prefix with the given value added on (regardless of casing).
  • ConstexprVariableIgnoredRegexp โ€” identifier naming checks won't be enforced for constexpr variable names matching this regular expression.
  • ConstexprVariableSuffix โ€” when defined, the check makes sure constexpr variable names get the suffix with the given value added on (regardless of casing).
  • ConstexprVariableHungarianPrefix โ€” when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐Ÿ’œ

For example using values of:

  • ConstexprVariableCase of lower_case
  • ConstexprVariablePrefix of pre_
  • ConstexprVariableSuffix of _post
  • ConstexprVariableHungarianPrefix of On

It identifies and/or transforms constexpr variable names as follows:

Before:

constexpr int ConstExpr_variable = MyConstant;

After:

constexpr int pre_constexpr_variable_post = MyConstant;

  • EnumCase โ€” when defined, the check makes sure enumeration names conform to the selected casing. ๐Ÿฆ‹
  • EnumPrefix โ€” when defined, the check makes sure enumeration names get the prefix with the given value added on (regardless of casing).
  • EnumIgnoredRegexp โ€” identifier naming checks won't be enforced for enumeration names matching this regular expression. Leave her be, she's booked and busy.
  • EnumSuffix โ€” when defined, the check makes sure enumeration names get the suffix with the given value added on (regardless of casing).

For example using values of:

  • EnumCase of lower_case
  • EnumPrefix of pre_
  • EnumSuffix of _post

It identifies and/or transforms enumeration names as follows:

Before:

enum FOO { One, Two, Three };

After:

enum pre_foo_post { One, Two, Three };

  • EnumConstantCase โ€” when defined, the check makes sure enumeration constant names conform to the selected casing. ๐ŸŒˆ
  • EnumConstantPrefix โ€” when defined, the check makes sure enumeration constant names get the prefix with the given value added on (regardless of casing).
  • EnumConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for enumeration constant names matching this regular expression. ๐Ÿ™ˆ
  • EnumConstantSuffix โ€” when defined, the check makes sure enumeration constant names get the suffix with the given value added on (regardless of casing).
  • EnumConstantHungarianPrefix โ€” when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐Ÿ‘‘

For example using values of:

  • EnumConstantCase of lower_case
  • EnumConstantPrefix of pre_
  • EnumConstantSuffix of _post
  • EnumConstantHungarianPrefix of On

It identifies and/or transforms enumeration constant names as follows:

Before:

enum FOO { One, Two, Three };

After:

enum FOO { pre_One_post, pre_Two_post, pre_Three_post };

  • FunctionCase โ€” when defined, the check makes sure function names conform to the selected casing. ๐Ÿ”ฅ
  • FunctionPrefix โ€” when defined, the check makes sure function names get the prefix with the given value added on (regardless of casing).
  • FunctionIgnoredRegexp โ€” identifier naming checks won't be enforced for function names matching this regular expression. Not me skipping the function, she's got immunity. ๐Ÿ’€
  • FunctionSuffix โ€” when defined, the check makes sure function names get the suffix with the given value added on (regardless of casing).

For example using values of:

  • FunctionCase of lower_case
  • FunctionPrefix of pre_
  • FunctionSuffix of _post

It identifies and/or transforms function names as follows:

Before:

char MY_Function_string();

After:

char pre_my_function_string_post();

  • GetConfigPerFile โ€” when true, the check will look for the configuration for where an identifier is declared. This is so useful when your included header files are using a whole different style than you. Default value is true. ๐Ÿซถ

  • GlobalConstexprVariableCase โ€” when defined, the check makes sure global constexpr variable names conform to the selected casing. โญ
  • GlobalConstexprVariablePrefix โ€” when defined, the check makes sure global constexpr variable names will add the prefixed with the given value (regardless of casing).
  • GlobalConstexprVariableIgnoredRegexp โ€” identifier naming checks won't be enforced for global constexpr variable names matching this regular expression.
  • GlobalConstexprVariableSuffix โ€” when defined, the check makes sure global constexpr variable names get the suffix with the given value added on (regardless of casing).
  • GlobalConstexprVariableHungarianPrefix โ€” when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐ŸŽ€

For example using values of:

  • GlobalConstexprVariableCase of lower_case
  • GlobalConstexprVariablePrefix of pre_
  • GlobalConstexprVariableSuffix of _post
  • GlobalConstexprVariableHungarianPrefix of On

It identifies and/or transforms global constexpr variable names as follows:

Before:

constexpr unsigned ImportantValue = 69;

After:

constexpr unsigned pre_important_value_post = 69;

  • GlobalConstantCase โ€” when defined, the check makes sure global constant names conform to the selected casing. ๐ŸŒธ
  • GlobalConstantPrefix โ€” when defined, the check makes sure global constant names get the prefix with the given value added on (regardless of casing).
  • GlobalConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for global constant names matching this regular expression. Hands off. ๐Ÿ™ˆ
  • GlobalConstantSuffix โ€” when defined, the check makes sure global constant names get the suffix with the given value added on (regardless of casing).
  • GlobalConstantHungarianPrefix โ€” when enabled, the check ensures the declared identifier will have a Hungarian notation prefix based on the declared type. ๐Ÿ’œ
  • GlobalConstantPointerCase โ€” set this and the check makes sure your global constant pointer names match the casing you picked. ๐Ÿ’…
  • GlobalConstantPointerPrefix โ€” set this and the check adds the prefix you give it to global constant pointer names (casing is irrelevant here, the prefix just gets slapped on). โœจ
  • GlobalConstantPointerIgnoredRegexp โ€” if a global constant pointer name matches this regex, the naming checks justโ€ฆ won't touch it. Leave it alone, bestie. ๐Ÿ™ˆ
  • GlobalConstantPointerSuffix โ€” set this and the check adds the suffix you give it to global constant pointer names (again, no matter the casing). ๐ŸŽ€
  • GlobalConstantPointerHungarianPrefix โ€” turn this on and the check makes sure the declared identifier gets a Hungarian notation prefix based on its declared type. ๐Ÿ‘‘

So like, 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 โ€” set this and the check makes sure global function names match the casing you chose. ๐ŸŒธ
  • GlobalFunctionPrefix โ€” set this and the check adds your prefix to global function names (regardless of casing, fr fr). ๐Ÿฆ‹
  • GlobalFunctionIgnoredRegexp โ€” global function names matching this regex? The naming checks are simply not enforcing anything on them. ๐Ÿ˜Œ
  • GlobalFunctionSuffix โ€” set this and the check adds your suffix to global function names (yep, 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);

  • GlobalPointerCase โ€” set this and the check makes sure global pointer names conform to the selected casing. โญ
  • GlobalPointerPrefix โ€” set this and the check adds your prefix to global pointer names (casing who? doesn't matter). โœจ
  • GlobalPointerIgnoredRegexp โ€” global pointer names that match this regex get skipped by the naming checks entirely. ๐Ÿซถ
  • GlobalPointerSuffix โ€” set this and the check adds your suffix to global pointer names (regardless of casing, always). ๐ŸŽ€
  • GlobalPointerHungarianPrefix โ€” when enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on its 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 โ€” set this and the check makes sure global variable names conform to the selected casing. ๐ŸŒˆ
  • GlobalVariablePrefix โ€” set this and the check adds your prefix to global variable names (regardless of casing). ๐Ÿ’œ
  • GlobalVariableIgnoredRegexp โ€” global variable names matching this regexp? The naming checks won't be enforced on them, periodt. ๐Ÿ™ˆ
  • GlobalVariableSuffix โ€” set this and the check adds your suffix to global variable names (regardless of casing, bestie). ๐Ÿ“
  • GlobalVariableHungarianPrefix โ€” when enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on its 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 like main or wmain won't have the naming checks enforced on their parameter names. Default value is false. ๐Ÿ˜Œ

  • InlineNamespaceCase โ€” set this and the check makes sure inline namespace names conform to the selected casing. โœจ
  • InlineNamespacePrefix โ€” set this and the check adds your prefix to inline namespace names (regardless of casing). ๐ŸŽ€
  • InlineNamespaceIgnoredRegexp โ€” inline namespace names matching this regexp are free from the naming checks. ๐Ÿ’…
  • InlineNamespaceSuffix โ€” set this and the check adds your suffix to inline namespace 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 namespace names like this:

Before:

namespace FOO_NS {
inline namespace InlineNamespace {
...
}
} // namespace FOO_NS

After:

namespace FOO_NS {
inline namespace pre_inlinenamespace_post {
...
}
} // namespace FOO_NS

  • LocalConstexprVariableCase โ€” set this and the check makes sure local constexpr variable names conform to the selected casing. ๐ŸŒธ
  • LocalConstexprVariablePrefix โ€” set this and the check will add the prefixed with the given value to local constexpr variable names (regardless of casing). ๐Ÿ’–
  • LocalConstexprVariableIgnoredRegexp โ€” local constexpr variable names matching this regexp get left alone by the naming checks. ๐Ÿ˜Œ
  • LocalConstexprVariableSuffix โ€” set this and the check adds your suffix to local constexpr variable names (regardless of casing). โญ
  • LocalConstexprVariableHungarianPrefix โ€” when enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on its 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 โ€” set this and the check makes sure local constant names conform to the selected casing. ๐ŸŒˆ
  • LocalConstantPrefix โ€” set this and the check adds your prefix to local constant names (regardless of casing). ๐Ÿซถ
  • LocalConstantIgnoredRegexp โ€” local constant names that match this regexp don't get the naming checks enforced on them. ๐Ÿ™ˆ
  • LocalConstantSuffix โ€” set this and the check adds your suffix to local constant names (regardless of casing). ๐Ÿ“
  • LocalConstantHungarianPrefix โ€” when enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on its 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 โ€” set this and the check makes sure local constant pointer names conform to the selected casing. โœจ
  • LocalConstantPointerPrefix โ€” set this and the check adds your prefix to local constant pointer names (regardless of casing). ๐Ÿ’œ
  • LocalConstantPointerIgnoredRegexp โ€” local constant pointer names matching this regexp are exempt from the naming checks. ๐Ÿ˜Œ
  • LocalConstantPointerSuffix โ€” set this and the check adds your suffix to local constant pointer names (regardless of casing). ๐ŸŽ€
  • LocalConstantPointerHungarianPrefix โ€” when enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on its declared type. ๐Ÿ‘‘

For example using values of:

  • LocalConstantPointerCase of lower_case
  • LocalConstantPointerPrefix of pre_
  • LocalConstantPointerSuffix of _post
  • LocalConstantPointerHungarianPrefix of On

It identifies and/or transforms local constant pointer names like this:

Before:

void foo() { int const *local_Constant = 3; }

After:

void foo() { int const *pre_local_constant_post = 3; }

  • LocalPointerCase โ€” set this and the check makes sure local pointer names conform to the selected casing. ๐ŸŒธ
  • LocalPointerPrefix โ€” set this and the check adds your prefix to local pointer names (regardless of casing). ๐Ÿฆ‹
  • LocalPointerIgnoredRegexp โ€” local pointer names matching this regexp? Naming checks won't be enforced on them. ๐Ÿ’…
  • LocalPointerSuffix โ€” set this and the check adds your suffix to local pointer names (regardless of casing). โญ
  • LocalPointerHungarianPrefix โ€” when enabled, the check makes sure the declared identifier has a Hungarian notation prefix based on its declared type. ๐Ÿงš

For example using values of:

  • LocalPointerCase of lower_case
    • LocalPointerPrefix of pre_
    • LocalPointerSuffix of _post
    • LocalPointerHungarianPrefix of On

So the check finds your local pointer names and either calls them out or just straight-up renames them, like this:

Before:

void foo() { int *local_Constant; }

After:

void foo() { int *pre_local_constant_post; }

LocalVariableCase โœจ

Set this and the check makes sure your local variable names match whatever casing you picked. No arguments, it's the law now bestie ๐Ÿ’…

LocalVariablePrefix ๐ŸŒธ

Set this and local variable names get this prefix bolted on the front with the value you gave (casing who? don't know her, the prefix goes on regardless).

LocalVariableIgnoredRegexp ๐ŸŽ€

If a local variable name matches this regex, the naming checks justโ€ฆ leave it alone. Protected. Untouchable. periodt.

Like, say you roll with:

  • LocalVariableCase of CamelCase
  • LocalVariableIgnoredRegexp of \w{1,2}

Then any variable whose name is 2 characters or shorter gets excluded from the camel case check that all the other variables are getting. Tiny names stay tiny, no notes ๐Ÿซถ

LocalVariableSuffix ๐Ÿฆ‹

Set this and local variable names get this suffix tacked on the end with the value you gave (again, regardless of casing โ€” the suffix does not care).

LocalVariableHungarianPrefix ๐Ÿ‘‘

Turn this on and the check makes sure the declared identifier gets a Hungarian notation prefix, based on the type it was declared as. Fancy.

For example, using values of:

  • LocalVariableCase of lower_case
  • LocalVariablePrefix of pre_
  • LocalVariableSuffix of _post
  • LocalVariableHungarianPrefix of On

It'll spot and/or transform your local variable names like:

Before:

void foo() { int local_Constant; }

After:

void foo() { int pre_local_constant_post; }

MacroDefinitionCase ๐ŸŒˆ

When defined, the check makes sure macro definitions conform to whichever casing you selected.

MacroDefinitionPrefix โญ

When defined, the check makes macro definitions add the prefix with the given value (regardless of casing, obviously).

MacroDefinitionIgnoredRegexp ๐Ÿ’œ

Identifier naming checks won't be enforced for macro definitions matching this regular expression. They get a pass.

MacroDefinitionSuffix ๐Ÿงš

When defined, the check makes macro definitions add the suffix with the given value (regardless of casing, yes ma'am).

For example, using values of:

  • MacroDefinitionCase of lower_case
  • MacroDefinitionPrefix of pre_
  • MacroDefinitionSuffix of _post

It identifies and/or transforms macro definitions like:

Before:

#define MY_MacroDefinition

After:

#define pre_my_macro_definition_post
Note: This will not warn on builtin macros, or on macros defined on the command line using the -D flag. Those ones are simply not the vibe, they're exempt, we move on ๐Ÿ™ˆ

MemberCase ๐Ÿ’–

When defined, the check ensures member names conform to the selected casing.

MemberPrefix โœจ

When defined, the check ensures 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 ensures member names will add the suffix with the given value (regardless of casing).

MemberHungarianPrefix ๐Ÿฆ‹

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:

  • MemberCase of lower_case
  • MemberPrefix of pre_
  • MemberSuffix of _post
  • MemberHungarianPrefix of On

It identifies and/or transforms member names like so:

Before:

class Foo {
  char MY_ConstMember_string[4];
}

After:

class Foo {
  char pre_my_constmember_string_post[4];
}

MethodCase ๐Ÿ‘‘

When defined, the check ensures method names conform to the selected casing.

MethodPrefix ๐Ÿ’…

When defined, the check ensures method names 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.

MethodSuffix โญ

When defined, the check ensures 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:

Before:

class Foo {
  char MY_Method_string();
}

After:

class Foo {
  char pre_my_method_string_post();
}

NamespaceCase ๐ŸŒˆ

When defined, the check ensures namespace names conform to the selected casing.

NamespacePrefix ๐Ÿงš

When defined, the check ensures namespace names 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 ensures 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 {
...
}

ParameterCase ๐Ÿ“

When defined, the check ensures parameter names conform to the selected casing.

ParameterPrefix ๐Ÿ’–

When defined, the check ensures 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 ensures parameter names will add the suffix with the given value (regardless of casing).

ParameterHungarianPrefix ๐Ÿ‘‘

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:

  • ParameterCase of lower_case
  • ParameterPrefix of pre_
  • ParameterSuffix of _post
  • ParameterHungarianPrefix of On

It identifies and/or transforms parameter names like:

Before:

void GLOBAL_FUNCTION(int PARAMETER_1, int const CONST_parameter);

After:

void GLOBAL_FUNCTION(int pre_parameter_post, int const CONST_parameter);

ParameterPackCase ๐ŸŽ€

When defined, the check ensures parameter pack names conform to the selected casing.

ParameterPackPrefix ๐ŸŒธ

When defined, the check ensures 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 ensures 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 so:

Before:

template <typename... TYPE_parameters> {
  void FUNCTION(int... TYPE_parameters);
}

After:

template <typename... TYPE_parameters> {
  void FUNCTION(int... pre_type_parameters_post);
}

PointerParameterCase ๐Ÿ’…

When defined, the check ensures pointer parameter names conform to the selected casing.

PointerParameterPrefix โญ

When defined, the check ensures 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 ensures pointer parameter names will add the suffix with the given value (regardless of casing).

PointerParameterHungarianPrefix ๐Ÿ’œ

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:

  • PointerParameterCase of lower_case
  • PointerParameterPrefix of pre_
  • PointerParameterSuffix of _post
  • PointerParameterHungarianPrefix of On

It identifies and/or transforms pointer parameter names like:

Before:

void FUNCTION(int *PARAMETER);

After:

void FUNCTION(int *pre_parameter_post);

PrivateMemberCase ๐Ÿซถ

When defined, the check ensures private member names conform to the selected casing.

PrivateMemberPrefix ๐Ÿ“

When defined, the check ensures private member names will add the prefix with the given value (regardless of casing).

PrivateMemberIgnoredRegexp ๐Ÿ”ฅ

Identifier naming checks won't be enforced for private member names matching this regular expression.

PrivateMemberSuffix ๐Ÿ‘‘

When defined, the check ensures private member names will add the suffix with the given value (regardless of casing).

PrivateMemberHungarianPrefix โœจ

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:

  • PrivateMemberCase of lower_case
  • PrivateMemberPrefix of pre_
  • PrivateMemberSuffix of _post
  • PrivateMemberHungarianPrefix of On

It identifies and/or transforms private member names like:

Before:

class Foo {
private:
  int Member_Variable;
}

After:

class Foo {
private:
  int pre_member_variable_post;
}

PrivateMethodCase ๐Ÿ’–

When defined, the check ensures private method names conform to the selected casing.

PrivateMethodPrefix ๐ŸŒธ

When defined, the check ensures 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 this one's defined, the check makes sure private method names get the suffix you hand it glued onto the end (and it does not care about casing, bestie, it just does the thing).

Like, say you're rocking:

  • PrivateMethodCase of lower_case
  • PrivateMethodPrefix of pre_
  • PrivateMethodSuffix of _post

It finds and/or transforms private method names like so:

Before:

class Foo {
private:
  int Member_Method();
}

After:

class Foo {
private:
  int pre_member_method_post();
}
  • ProtectedMemberCase โ€” when defined, the check makes sure protected member names match up with the casing you picked. vibe check passed or else ๐Ÿ’œ
  • ProtectedMemberPrefix โ€” when defined, the check makes sure protected member names get the prefix you give it slapped on the front (casing? doesn't matter, periodt).
  • ProtectedMemberIgnoredRegexp โ€” identifier naming checks just won't be enforced for protected member names that match this regular expression. they're exempt, they're off the hook, they're free ๐Ÿฆ‹
  • ProtectedMemberSuffix โ€” when defined, the check makes sure protected member names get the suffix you give it added to the end (regardless of casing, fr fr).
  • ProtectedMemberHungarianPrefix โ€” when this is enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the type it was declared with. type-aware glow-up โœจ

For example, using values of:

  • ProtectedMemberCase of lower_case
  • ProtectedMemberPrefix of pre_
  • ProtectedMemberSuffix of _post
  • ProtectedMemberHungarianPrefix of On

It finds and/or transforms protected member names like this:

Before:

class Foo {
protected:
  int Member_Variable;
}

After:

class Foo {
protected:
  int pre_member_variable_post;
}
  • ProtectedMethodCase โ€” when defined, the check makes sure protected method names match the casing you selected.
  • ProtectedMethodPrefix โ€” when defined, the check makes sure protected method names get the prefix you give it added up front (regardless of casing).
  • ProtectedMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for protected method names that match this regular expression. they get a pass ๐Ÿ’…
  • ProtectedMethodSuffix โ€” when defined, the check makes sure protected method names get the suffix you give it tacked on the end (regardless of casing).

For example, using values of:

  • ProtectedMethodCase of lower_case
  • ProtectedMethodPrefix of pre_
  • ProtectedMethodSuffix of _post

It finds and/or transforms protected method names like so:

Before:

class Foo {
protected:
  int Member_Method();
}

After:

class Foo {
protected:
  int pre_member_method_post();
}
  • ProtectedMemberCase โ€” when defined, the check makes sure public member names conform to the casing you picked. main character naming energy ๐Ÿ‘‘
  • ProtectedMemberPrefix โ€” when defined, the check makes sure public member names get the prefix you give it added on the front (regardless of casing).
  • ProtectedMemberIgnoredRegexp โ€” identifier naming checks won't be enforced for public member names matching this regular expression.
  • ProtectedMemberSuffix โ€” when defined, the check makes sure public member names get the suffix you give it added on the end (regardless of casing).
  • ProtectedMemberHungarianPrefix โ€” when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type. no cap, it reads the type ๐Ÿ“

For example, using values of:

  • PublicMemberCase of lower_case
  • PublicMemberPrefix of pre_
  • PublicMemberSuffix of _post
  • PublicMemberHungarianPrefix of On

It finds and/or transforms public member names like this:

Before:

class Foo {
public:
  int Member_Variable;
}

After:

class Foo {
public:
  int pre_member_variable_post;
}
  • PublicMethodCase โ€” when defined, the check makes sure public method names conform to the selected casing.
  • PublicMethodPrefix โ€” when defined, the check makes sure public method names get the prefix you give it added up front (regardless of casing).
  • PublicMethodIgnoredRegexp โ€” identifier naming checks won't be enforced for public method names matching this regular expression. skipped, not sorry ๐Ÿ’€
  • PublicMethodSuffix โ€” when defined, the check makes sure public method names get the suffix you give it added at the end (regardless of casing).

For example, using values of:

  • PublicMethodCase of lower_case
  • PublicMethodPrefix of pre_
  • PublicMethodSuffix of _post

It finds and/or transforms public method names like so:

Before:

class Foo {
public:
  int Member_Method();
}

After:

class Foo {
public:
  int pre_member_method_post();
}
  • ScopedEnumConstantCase โ€” when defined, the check makes sure scoped enum constant names conform to the casing you selected.
  • ScopedEnumConstantPrefix โ€” when defined, the check makes sure scoped enum constant names get the prefix you give it added on the front (regardless of casing).
  • ScopedEnumConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for scoped enum constant names matching this regular expression.
  • ScopedEnumConstantSuffix โ€” when defined, the check makes sure scoped enum constant names get the suffix you give it added on the end (regardless of casing).
  • ScopedEnumConstantHungarianPrefix โ€” when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type. obsessed with this one actually ๐ŸŽ€

For example, using values of:

  • ScopedEnumConstantCase of lower_case
  • ScopedEnumConstantPrefix of pre_
  • ScopedEnumConstantSuffix of _post
  • ScopedEnumConstantHungarianPrefix of On

It finds and/or transforms enumeration constant names like this:

Before:

enum class FOO { One, Two, Three };

After:

enum class FOO { pre_One_post, pre_Two_post, pre_Three_post };
  • StaticConstexprVariableCase โ€” when defined, the check makes sure static constexpr variable names conform to the casing you picked.
  • StaticConstexprVariablePrefix โ€” when defined, the check makes sure static constexpr variable names get the prefix added with the value you give it (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 makes sure static constexpr variable names get the suffix you give it added on the end (regardless of casing).
  • StaticConstexprVariableHungarianPrefix โ€” when enabled, the check makes sure the declared identifier gets 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

It finds and/or transforms static constexpr variable names like so:

Before:

static unsigned constexpr MyConstexprStatic_array[] = {1, 2, 3};

After:

static unsigned constexpr pre_my_constexpr_static_array_post[] = {1, 2, 3};
  • StaticConstantCase โ€” when defined, the check makes sure static constant names conform to the casing you chose.
  • StaticConstantPrefix โ€” when defined, the check makes sure static constant names get the prefix you give it added on the front (regardless of casing).
  • StaticConstantIgnoredRegexp โ€” identifier naming checks won't be enforced for static constant names matching this regular expression. untouchable ๐Ÿ’…
  • StaticConstantSuffix โ€” when defined, the check makes sure static constant names get the suffix you give it added on the end (regardless of casing).
  • StaticConstantHungarianPrefix โ€” when enabled, the check makes sure the declared identifier gets 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

It finds and/or transforms static constant names like this:

Before:

static unsigned const MyConstStatic_array[] = {1, 2, 3};

After:

static unsigned const pre_myconststatic_array_post[] = {1, 2, 3};
  • StaticVariableCase โ€” when defined, the check makes sure static variable names conform to the casing you selected.
  • StaticVariablePrefix โ€” when defined, the check makes sure static variable names get the prefix you give it added on the front (regardless of casing).
  • StaticVariableIgnoredRegexp โ€” identifier naming checks won't be enforced for static variable names matching this regular expression.
  • StaticVariableSuffix โ€” when defined, the check makes sure static variable names get the suffix you give it added on the end (regardless of casing).
  • StaticVariableHungarianPrefix โ€” when enabled, the check makes sure the declared identifier gets a Hungarian notation prefix based on the declared type. type-driven, we love to see it ๐ŸŒธ

For example, using values of:

  • StaticVariableCase of lower_case
  • StaticVariablePrefix of pre_
  • StaticVariableSuffix of _post
  • StaticVariableHungarianPrefix of On

It finds and/or transforms static variable names like so:

Before:

static unsigned MyStatic_array[] = {1, 2, 3};

After:

static unsigned pre_mystatic_array_post[] = {1, 2, 3};
  • StructCase โ€” when defined, the check makes sure struct names conform to the casing you picked.
  • StructPrefix โ€” when defined, the check makes sure struct names get the prefix you give it added on the front (regardless of casing).
  • StructIgnoredRegexp โ€” identifier naming checks won't be enforced for struct names matching this regular expression.
  • StructSuffix โ€” when defined, the check makes sure struct names get the suffix you give it added on the end (regardless of casing).

For example, using values of:

  • StructCase of lower_case
  • StructPrefix of pre_
  • StructSuffix of _post

It finds and/or transforms struct names like this:

Before:

struct FOO {
  FOO();
  ~FOO();
};

And after:

struct pre_foo_post {
  pre_foo_post();
  ~pre_foo_post();
};

  • TemplateParameterCase โ€” when you set this, bestie, the check makes sure your template parameter names match whatever casing you picked. no cap. โœจ
  • TemplateParameterPrefix โ€” when defined, the check makes template parameter names slap the given prefix on the front (casing who? don't know her โ€” this happens regardless of casing). ๐ŸŽ€
  • TemplateParameterIgnoredRegexp โ€” if a template parameter name matches this regex, the identifier naming checks just... won't bother it. she's exempt, periodt. ๐Ÿ’…
  • TemplateParameterSuffix โ€” when defined, template parameter names get the given suffix tacked on the end (again, regardless of casing). ๐Ÿฆ‹

For example, using values of:

  • TemplateParameterCase of lower_case
  • TemplateParameterPrefix of pre_
  • TemplateParameterSuffix of _post

It identifies and/or transforms template parameter names like this:

Before:

template <typename T> class Foo {};

After:

template <typename pre_t_post> class Foo {};

  • TemplateTemplateParameterCase โ€” when defined, the check makes sure your template template parameter names conform to the selected casing. (yes, template template. it's giving inception but make it naming conventions ๐ŸŒ€)
  • TemplateTemplateParameterPrefix โ€” when defined, the check makes template template parameter names 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 makes template template parameter names 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

It identifies and/or transforms template template parameter names like this:

Before:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... TYPE_parameters>

After:

template <template <typename> class pre_tpl_parameter_post, int COUNT_params,
          typename... TYPE_parameters>

  • TypeAliasCase โ€” when defined, the check makes sure type alias names conform to the selected casing. โœจ
  • TypeAliasPrefix โ€” when defined, the check makes type alias names 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 makes type alias names 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

It identifies and/or transforms type alias names like this:

Before:

using MY_STRUCT_TYPE = my_structure;

After:

using pre_my_struct_type_post = my_structure;

  • TypedefCase โ€” when defined, the check makes sure typedef names conform to the selected casing. ๐Ÿ’–
  • TypedefPrefix โ€” when defined, the check makes typedef names 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 makes typedef names 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

It identifies and/or transforms typedef names like this:

Before:

typedef int MYINT;

After:

typedef int pre_myint_post;

  • TypeTemplateParameterCase โ€” when defined, the check makes sure type template parameter names conform to the selected casing. ๐ŸŒธ
  • TypeTemplateParameterPrefix โ€” when defined, the check makes type template parameter names 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.
  • TypeTemplateParameterSuffix โ€” when defined, the check makes type template parameter names 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

It identifies and/or transforms type template parameter names like this:

Before:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... TYPE_parameters>

After:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... pre_type_parameters_post>

  • UnionCase โ€” when defined, the check makes sure union names conform to the selected casing. ๐Ÿค
  • UnionPrefix โ€” when defined, the check makes union names 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 makes union names 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

It identifies and/or transforms union names like this:

Before:

union FOO {
  int a;
  char b;
};

After:

union pre_foo_post {
  int a;
  char b;
};

  • ValueTemplateParameterCase โ€” when defined, the check makes sure value template parameter names conform to the selected casing. ๐Ÿ”ฅ
  • ValueTemplateParameterPrefix โ€” when defined, the check makes value template parameter names 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 makes value template parameter names 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

It identifies and/or transforms value template parameter names like this:

Before:

template <template <typename> class TPL_parameter, int COUNT_params,
          typename... TYPE_parameters>

After:

template <template <typename> class TPL_parameter, int pre_count_params_post,
          typename... TYPE_parameters>

  • VariableCase โ€” when defined, the check makes sure variable names conform to the selected casing. โญ
  • VariablePrefix โ€” when defined, the check makes variable names 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 makes variable names 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

It identifies and/or transforms variable names like this:

Before:

unsigned MyVariable;

After:

unsigned pre_myvariable_post;

  • VirtualMethodCase โ€” when defined, the check makes sure virtual method names conform to the selected casing. ๐Ÿงš
  • VirtualMethodPrefix โ€” when defined, the check makes virtual method names 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 makes virtual method names 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

It identifies and/or transforms virtual method names like this:

Before:

class Foo {
public:
  virtual int MemberFunction();
}

After:

class Foo {
public:
  virtual int pre_member_function_post();
}

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

Ok so bestie, here's the default mapping table for Hungarian Notation โ€” it's the thing that maps a Decl to its prefix string. And yeah, you can absolutely have your own style in a config file, go off ๐Ÿ’…


๐ŸŽ€ The Primitive Type column (primitive types โ†’ prefix)

  • int8_t โ†’ i8
  • int16_t โ†’ i16
  • int32_t โ†’ i32
  • int64_t โ†’ i64
  • uint8_t โ†’ u8
  • uint16_t โ†’ u16
  • uint32_t โ†’ u32
  • uint64_t โ†’ u64
  • char8_t โ†’ c8
  • char16_t โ†’ c16
  • char32_t โ†’ c32
  • float โ†’ f
  • double โ†’ d
  • char โ†’ c
  • bool โ†’ b
  • _Bool โ†’ b
  • int โ†’ i
  • size_t โ†’ n
  • short โ†’ s
  • signed โ†’ i
  • unsigned โ†’ u
  • long โ†’ l
  • long long โ†’ ll
  • unsigned long โ†’ ul
  • long double โ†’ ld
  • ptrdiff_t โ†’ p
  • wchar_t โ†’ wc
  • short int โ†’ si
  • short โ†’ s

๐Ÿฆ‹ The middle Type column (type โ†’ prefix)

  • signed int โ†’ si
  • signed short โ†’ ss
  • signed short int โ†’ ssi
  • signed long long int โ†’ slli
  • signed long long โ†’ sll
  • signed long int โ†’ sli
  • signed long โ†’ sl
  • signed โ†’ s
  • unsigned long long int โ†’ ulli
  • unsigned long long โ†’ ull
  • unsigned long int โ†’ uli
  • unsigned long โ†’ ul
  • unsigned short int โ†’ usi
  • unsigned short โ†’ us
  • unsigned int โ†’ ui
  • unsigned char โ†’ uc
  • unsigned โ†’ u
  • long long int โ†’ lli
  • long double โ†’ ld
  • long long โ†’ ll
  • long int โ†’ li
  • long โ†’ l
  • ptrdiff_t โ†’ p
  • void โ†’ none

๐Ÿ’œ The Microsoft Type column (Microsoft type โ†’ prefix)

  • BOOL โ†’ b
  • BOOLEAN โ†’ b
  • BYTE โ†’ by
  • CHAR โ†’ c
  • UCHAR โ†’ uc
  • SHORT โ†’ s
  • USHORT โ†’ us
  • WORD โ†’ w
  • DWORD โ†’ dw
  • DWORD32 โ†’ dw32
  • DWORD64 โ†’ dw64
  • LONG โ†’ l
  • ULONG โ†’ ul
  • ULONG32 โ†’ ul32
  • ULONG64 โ†’ ul64
  • ULONGLONG โ†’ ull
  • HANDLE โ†’ h
  • INT โ†’ i
  • INT8 โ†’ i8
  • INT16 โ†’ i16
  • INT32 โ†’ i32
  • INT64 โ†’ i64
  • UINT โ†’ ui
  • UINT8 โ†’ u8
  • UINT16 โ†’ u16
  • UINT32 โ†’ u32
  • UINT64 โ†’ u64
  • PVOID โ†’ p

There are, like, more trivial options for Hungarian Notation too, so let's get into those bestie ๐Ÿซถ

  • HungarianNotation.General.* โ€” these ones are not belonging to any specific Decl, they're just vibing on their own.
  • HungarianNotation.CString.* โ€” options for NULL-terminated string.
  • HungarianNotation.DerivedType.* โ€” options for derived types.
  • HungarianNotation.PrimitiveType.* โ€” options for primitive types.
  • HungarianNotation.UserDefinedType.* โ€” options for user-defined types.

โœจ Options for Hungarian Notation โœจ

  • HungarianNotation.General.TreatStructAsClass

  • 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 is gonna treat the naming of a struct as if it were a class. The default value is false. bestie it's literally just that ๐Ÿ’…
  • HungarianNotation.DerivedType.Array โ€” when defined, the check will make sure the variable name gets the prefix with the given string. The default prefix is a.
  • HungarianNotation.DerivedType.Pointer โ€” when defined, the check will make sure the variable name gets the prefix with the given string. The default prefix is p.
  • HungarianNotation.DerivedType.FunctionPointer โ€” when defined, the check will make sure the variable name gets the prefix with the given string. The 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 will make sure the variable name gets the prefix with the given string. The default prefix is sz.
  • HungarianNotation.CString.CharArray โ€” when defined, the check will make sure the variable name gets the prefix with the given string. The default prefix is sz.
  • HungarianNotation.CString.WideCharPointer โ€” when defined, the check will make sure the variable name gets the prefix with the given string. The default prefix is wsz.
  • HungarianNotation.CString.WideCharArray โ€” when defined, the check will make sure the variable name gets the prefix with the given string. The 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 will make sure the variable name of the involved primitive types gets the prefix with the given string. The default prefixes are defined in the default mapping table. iykyk ๐Ÿ’–
  • HungarianNotation.UserDefinedType.* โ€” when defined, the check will make sure the variable name of the involved primitive types gets the prefix with the given string. The default prefixes are defined in the default mapping table.

Before:

int8_t   ValueI8      = 0;
int16_t  ValueI16     = 0;
int32_t  ValueI32     = 0;
int64_t  ValueI64     = 0;
uint8_t  ValueU8      = 0;
uint16_t ValueU16     = 0;
uint32_t ValueU32     = 0;
uint64_t ValueU64     = 0;
float    ValueFloat   = 0.0;
double   ValueDouble  = 0.0;
ULONG    ValueUlong   = 0;
DWORD    ValueDword   = 0;

After:

int8_t   i8ValueI8    = 0;
int16_t  i16ValueI16  = 0;
int32_t  i32ValueI32  = 0;
int64_t  i64ValueI64  = 0;
uint8_t  u8ValueU8    = 0;
uint16_t u16ValueU16  = 0;
uint32_t u32ValueU32  = 0;
uint64_t u64ValueU64  = 0;
float    fValueFloat  = 0.0;
double   dValueDouble = 0.0;
ULONG    ulValueUlong = 0;
DWORD    dwValueDword = 0;

readability-implicit-bool-cast

This check has been renamed to readability-implicit-bool-conversion โœจ (link's right there in the original, bestie, we just can't click it here ๐Ÿซถ).


readability-implicit-bool-conversion

Okay so this check can be used to find implicit conversions between built-in types and booleans. Depending on your use case, it might just help with readability of the code, or in some cases it'll point to potential bugs which stay unnoticed because of implicit conversions. fr fr ๐Ÿ‘€

The following is a real-world example of a bug which 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 result of an unsuccessful refactoring, where the type of m_foo changed from bool to int. The programmer forgot to change all occurrences of bool, and the remaining code is no longer correct, yet it still compiles without any visible warnings. not me leaving a landmine in the codebase ๐Ÿ’€

In addition to issuing warnings, fix-it hints are provided to help solve the reported issues. This can be used for improving readability of code, for example:

void conversionsToBool() {
  float floating;
  bool boolean = floating;
  // ^ propose replacement: bool boolean = floating != 0.0f;

  int integer;
  if (integer) {}
  // ^ propose replacement: if (integer != 0) {}

  int* pointer;
  if (!pointer) {}
  // ^ propose replacement: if (pointer == nullptr) {}

  while (1) {}
  // ^ propose replacement: while (true) {}
}

void functionTakingInt(int param);

void conversionsFromBool() {
  bool boolean;
  functionTakingInt(boolean);
  // ^ propose replacement: functionTakingInt(static_cast<int>(boolean));

  functionTakingInt(true);
  // ^ propose replacement: functionTakingInt(1);
}

In general, the following conversion types are checked:

  • integer expression/literal to boolean (conversion from a single bit bitfield to boolean is explicitly allowed, since there's no ambiguity / information loss in this case),
  • floating expression/literal to boolean,
  • pointer/pointer to member/nullptr/NULL to boolean,
  • boolean expression/literal to integer (conversion from boolean to a single bit bitfield is explicitly allowed),
  • boolean expression/literal to floating.

The rules for generating fix-it hints are:

  • in case of conversions from other built-in type to bool, an explicit comparison is proposed to make it 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 is 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 additional accommodations are made for pre-C++11 dialects:

  • false literal conversion to pointer is detected,
  • instead of nullptr literal, 0 is proposed as replacement.

Some additional accommodations are made for C:

  • bool (or _Bool) operands in logical operators (&&, ||) are ignored.

Occurrences of implicit conversions inside macros and template instantiations are deliberately ignored, as it is not clear how to deal with such cases. we simply do not perceive them ๐Ÿ‘‘

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

Find function declarations which differ in parameter names. ๐Ÿ•ต๏ธโ€โ™€๏ธ

Example:

// in foo.hpp:
void foo(int a, int b, int c);

// in foo.cpp:
void foo(int d, int e, int f); // warning

This check should help to enforce consistency in large projects, where it often happens that a definition of a function is refactored, changing the parameter names, but its declaration in the header file is not updated. With this check, we can easily find and correct such inconsistencies, keeping declaration and definition always in sync. iconic behaviour honestly ๐Ÿ’…

Unnamed parameters are allowed and are not taken into account when comparing function declarations, for example:

void foo(int a);
void foo(int); // no warning

One name is also allowed to be a case-insensitive prefix/suffix of the other:

void foo(int count);
void foo(int count_input) { // no warning
  int count = adjustCount(count_input);
}

To help with refactoring, in some cases fix-it hints are generated to align parameter names to a single naming convention. This works with the assumption that the function definition is the most up-to-date version, as it directly references parameter names in its body. Example:

void foo(int a); // warning and fix-it hint (replace "a" to "b")
int foo(int b) { return b + 2; } // definition with use of "b"

In the case of multiple redeclarations or function template specializations, a warning is issued for every redeclaration or specialization inconsistent with the definition or the first declaration seen in a translation unit. every single one, no skips ๐Ÿ’–

Options

  • IgnoreMacros โ€” if this option is set to true (default is true), the check will not warn about names declared inside macros.
  • Strict โ€” if this is set to true (default is false), then names have to match exactly (or just be absent). no vibes-based guessing ๐Ÿ’…

โœจ readability-inconsistent-ifelse-braces โœจ

Spots if/else statements where one branch is wearing braces and the other one simply is not. the audacity ๐Ÿซ 

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 โœจ

Catches local variable declarations that are out here declaring more than one variable at once and tries to refactor the code into one statement per declaration. one variable, one statement, one moment of peace ๐Ÿงš

The automatic code-transformation will use the same indentation as the original for every created statement and add a line break after each statement. it also keeps the order of the variable declarations consistent, too. we love a consistent queen ๐Ÿ‘‘

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. heads up bestie: structured bindings are not considered.

// It is not possible to transform this declaration and doing the declaration
// before the loop will increase the scope of the variable 'Begin' and 'End'
// which is undesirable.
for (int Begin = 0, End = 100; Begin < End; ++Begin);
if (int Begin = 42, Result = some_function(Begin); Begin == Result);

// It is not possible to transform this declaration because the result is
// not functionality preserving as 'j' and 'k' would not be part of the
// 'if' statement anymore.
if (SomeCondition())
  int i = 42, j = 43, k = function(i,j);

๐Ÿ’ซ Limitations ๐Ÿ’ซ

Global variables and member variables are excluded. not invited to the party, sorry ๐Ÿšซ

The check currently does not support the automatic transformation of member-pointer-types.

struct S {
  int a;
  const int b;
  void f() {}
};

void f() {
  // Only a diagnostic message is emitted
  int S::*p = &S::a, S::*const q = &S::a;
}

Also? 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, to avoid unexpected side-effects due to macros. safety first, always ๐Ÿ›ก๏ธ

#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 girl where did that number even come from ๐Ÿ˜ญ

Lots of coding guidelines advise replacing magic values with symbolic constants to improve readability. here are a few references:

Examples of magic values:

template<typename T, size_t N>
struct CustomType {
   T arr[N];
};

struct OtherType {
   CustomType<int, 30> container;
}
CustomType<int, 30> values;

double circleArea = 3.1415926535 * radius * radius;

double totalCharge = 1.08 * itemPrice;

int getAnswer() {
   return -3; // FILENOTFOUND
}

for (int mm = 1; mm <= 12; ++mm) {
   std::cout << month[mm] << '\n';
}

Example with magic values refactored:

template<typename T, size_t N>
struct CustomType {
   T arr[N];
};

const size_t NUMBER_OF_ELEMENTS = 30;
using containerType = CustomType<int, NUMBER_OF_ELEMENTS>;

struct OtherType {
   containerType container;
}
containerType values;

double circleArea = M_PI * radius * radius;

const double TAX_RATE = 0.08;  // or make it variable and read from a file

double totalCharge = (1.0 + TAX_RATE) * itemPrice;

int getAnswer() {
   return E_FILE_NOT_FOUND;
}

for (int mm = 1; mm <= MONTHS_IN_A_YEAR; ++mm) {
   std::cout << month[mm] << '\n';
}

For integral literals, by default only 0 and 1 (and -1) integer values are accepted without a warning. this can be overridden with the IgnoredIntegerValues option. negative values are accepted if their absolute value is present in the IgnoredIntegerValues list. ๐Ÿงฎ

As a special case for integral values, all powers of two can be accepted without warning by enabling the IgnorePowersOf2IntegerValues option.

For floating point literals, by default the 0.0 floating point value is accepted without a warning. the set of ignored floating point literals can be configured using the IgnoredFloatingPointValues option. for each value in that set, the given string value is converted to a floating-point value representation used by the target architecture. if a floating-point literal value compares equal to one of the converted values, then that literal is not diagnosed by this check. and 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. โš ๏ธ

For each value in the IgnoredFloatingPointValues set, both the single-precision form and double-precision form are accepted (for example, if 3.14 is in the set, neither 3.14f nor 3.14 will produce a warning).

Scientific notation is supported for both source code input and option. alternatively, the check for the floating point numbers can be disabled for all floating point values by enabling the IgnoreAllFloatingPointValues option.

Since values 0 and 0.0 are so common as the base counter of loops, or initialization values for sums, they are always accepted without warning, even if not present in the respective ignored values list. periodt ๐Ÿ’…

๐Ÿ’ซ 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.
  • 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 can be made const because the functions don't use this in a non-const way. honestly iconic behaviour ๐Ÿ‘‘

This check tries to annotate methods according to logical constness (https://isocpp.org/wiki/faq/const-correctness#logical-vs-physical-state) (not physical constness). so it will only suggest adding a const qualifier to a non-const method if that 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 the 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. no sneaky pointer business allowed ๐Ÿšซ

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, plot twist: after you've applied all the glow-ups this check suggested, running it again might find even more chances to slap const on member functions. It's giving onion layers, bestie. ๐Ÿง…โœจ


readability-math-missing-parentheses ๐Ÿ’…

It's out here checking for missing parentheses in math expressions where operators have different priorities. Because order of operations matters and yes, we all say we remember PEMDAS but then we write a novel-length expression and suddenly nobody knows anything. ๐Ÿ˜ญ

Parens in math expressions make the order of operations crystal clear, especially when different-priority operators are vibing together. Lengthy or multiline expressions can straight up hide that order, and that's how coding errors sneak in. IDEs can help by highlighting parentheses, which is cute of them, but explicitly using parens also shows what the developer actually had in mind. Having them there reduces ambiguity and errors and generally promotes clearer, more maintainable code. Periodt. โœจ

Before:

int x = 1 + 2 * 3 - 4 / 5;

After:

int x = 1 + (2 * 3) - (4 / 5);

readability-misleading-indentation ๐Ÿซฃ

Correct indentation helps people actually understand your code. When the syntactic structure and the indentation disagree, that mismatch can hide serious problems. Missing braces also make code significantly harder to read, so bestie, use the braces. ๐ŸŽ€

The way to dodge the dangling else is to always check that an else belongs to the if that starts in the same column.

You can omit braces when the inner part of, like, an if statement only has one statement in it. Although in that case you should start the next statement in the same column as the if.

Examples:

// Dangling else:
if (cond1)
  if (cond2)
    foo1();
else
  foo2();  // Wrong indentation: else belongs to if(cond2) statement.

// Missing braces:
if (cond1)
  foo1();
  foo2();  // Not guarded by if(cond1).

Limitations

Heads up: this check only works as expected when tabs or spaces are used consistently and not mixed. No chaotic mix-and-match energy, please. ๐Ÿ’€


readability-misplaced-array-index ๐Ÿ”„

This check warns about unusual array index syntax. It's the fashion police but for Y[X]. ๐Ÿ‘‘

The following code has unusual array index syntax:

void f(int *X, int Y) {
  Y[X] = 0;
}

becomes

void f(int *X, int Y) {
  X[Y] = 0;
}

The check warns about such unusual syntax for readability reasons:

  • There are programmers that are not familiar with this unusual syntax.
  • It is possible that variables are mixed up.

readability-named-parameter ๐Ÿท๏ธ

Find functions with unnamed arguments. Name your params, bestie, they deserve an identity. โœจ

The check implements the following rule originating in the Google C++ Style Guide:

https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions

All parameters should have the same name in both the function declaration and definition. If a parameter is not utilized, its name can be commented out in a function definition.

int doingSomething(int a, int b, int c);

int doingSomething(int a, int b, int /*c*/) {
    // Ok: the third param is not used
    return a + b;
}

Corresponding cpplint.py check name: readability/function.

Options

  • InsertPlainNamesInForwardDecls โ€” If set to true, the check will insert parameter names without comments for forward declarations only. Otherwise, the check will insert parameter names as comments (e.g., /*param*/). Default 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. It's basically asking "girl, why is this not const yet?" ๐Ÿ’…

When const is used properly, many mistakes can be avoided. Advantages when using const properly:

  • prevent unintentional modification of data;
  • get additional warnings such as using uninitialized data;
  • make it easier for developers to see possible side effects.

This check is not strict about constness โ€” it only warns when the constness will make the function interface safer.

// warning here; the declaration "const char *p" would make the function
// interface safer.
char f1(char *p) {
  return *p;
}

// no warning; the declaration could be more const "const int * const p" but
// that does not make the function interface safer.
int f2(const int *p) {
  return *p;
}

// no warning; making x const does not make the function interface safer
int f3(int x) {
  return x;
}

// no warning; Technically, *p can be const ("const struct S *p"). But making
// *p const could be misleading. People might think that it's safe to pass
// const data to this function.
struct S { int *a; int *b; };
int f3(struct S *p) {
  *(p->a) = 0;
}

// no warning; p is referenced by an lvalue.
void f4(int *p) {
  int &x = *p;
}

readability-operators-representation ๐ŸŽญ

Enforces consistent token representation for invoked binary, unary and overloaded operators in C++ code. The check supports both traditional and alternative representations of operators, such as && and and, || and or, and so on. Pick a lane and stay in it, babes. โœจ

In the realm of C++ programming, developers have the option to choose between two distinct representations for operators: traditional token representation and alternative token representation. Traditional tokens use symbols, such as &&, ||, and !, while alternative tokens employ more descriptive words like and, or, and not. Both are iconic, honestly.

In the following mapping table, a comprehensive list of traditional and alternative tokens, along with their corresponding representations, is presented:

Token Representation Mapping Table ๐ŸŒˆ

  • Traditional && โ€” Alternative and
  • Traditional &= โ€” Alternative and_eq
  • Traditional & โ€” Alternative bitand
  • Traditional | โ€” Alternative bitor
  • Traditional ~ โ€” Alternative compl
  • Traditional ! โ€” Alternative not
  • Traditional != โ€” Alternative not_eq
  • Traditional || โ€” Alternative or
  • Traditional |= โ€” Alternative or_eq
  • Traditional ^ โ€” Alternative xor
  • Traditional ^= โ€” Alternative xor_eq

Example

// Traditional Token Representation:

if (!a||!b)
{
    // do something
}

// Alternative Token Representation:

if (not a or not b)
{
    // do something
}

Options

Because each representation has its own distinct benefits and drawbacks, the default configuration doesn't enforce either one. Explicit configuration is needed. No autopilot 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.

Developers do not need to enforce all operators, and can mix the representations as desired by specifying a semicolon-separated list of both traditional and alternative tokens in the configuration, such as and;||;not.

  • BinaryOperators โ€” This option allows you to specify a semicolon-separated list of binary operators for which you want to enforce specific token representation. The default value is empty string.
  • OverloadedOperators โ€” This option allows you to specify a semicolon-separated list of overloaded operators for which you want to enforce specific token representation. The default value is empty string.

readability-qualified-auto ๐Ÿ‘‘

Adds pointer qualifications to auto-typed variables that are deduced to pointers. Giving your auto its little star, as a treat. โญ

LLVM Coding Standards https://llvm.org/docs/CodingStandards.html#beware-unnecessary-copies-with-auto advises to make it obvious if an auto typed variable is a pointer. This check will transform auto to auto * when the type is deduced to be a pointer.

for (auto Data : MutatablePtrContainer) {
  change(*Data);
}
for (auto Data : ConstantPtrContainer) {
  observe(*Data);
}

Would be transformed into:

for (auto *Data : MutatablePtrContainer) {
  change(*Data);
}
for (const auto *Data : ConstantPtrContainer) {
  observe(*Data);
}

Note that const volatile qualified types will retain their const and volatile qualifiers. Pointers to pointers will not be fully qualified.

const auto Foo = cast<int *>(Baz1);
const auto Bar = cast<const int *>(Baz2);
volatile auto FooBar = cast<int *>(Baz3);
auto BarFoo = cast<int **>(Baz4);

Would be transformed into:

auto *const Foo = cast<int *>(Baz1);
const auto *const Bar = cast<const int *>(Baz2);
auto *volatile FooBar = cast<int *>(Baz3);
auto *BarFoo = cast<int **>(Baz4);

โœจ Options โœจ

AddConstToQualified

When it's set to true, the check goes ahead and adds 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 will be transformed into:

const auto *Foo1 = cast<const int *>(Bar1);
auto *Foo2 = cast<const int *>(Bar2);
auto &Foo3 = cast<const int &>(Bar3);

Otherwise it will be transformed into:

const auto *Foo1 = cast<const int *>(Bar1);
const auto *Foo2 = cast<const int *>(Bar2);
const auto &Foo3 = cast<const int &>(Bar3);
ngl bestie, watch out: in the LLVM alias, the default value is false. ๐Ÿ‘€

AllowedTypes

A semicolon-separated list of names of types to ignore when auto is deduced to that type or a pointer to that type. Note that this distinguishes type aliases from the original type, so specifying e.g. my_int will not suppress reports about int even if it is 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 string. 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. slay

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

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. it's giving clutter, bestie

โœจ 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. free real estate

โœจ 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 recommend their removal. Covers a 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. periodt.

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. likeโ€ฆ why ๐Ÿ™ˆ

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.

โœจ 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.


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.

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. fr fr, typos are the villain arc ๐Ÿ’€

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.

Before:

int f(int,int);
int (*p)(int, int) = &f;

int i = (**p)(10, 50);

After:

int f(int,int);
int (*p)(int, int) = &f;

int i = (*p)(10, 50);

one asterisk too many, bestie, we're not doing that


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. she's already inlined, stop double-dipping

โœจ 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-member-init ๐ŸŒธ

Finds member initializations that are unnecessary because the same default constructor would be called if they were not present.

โœจ Example โœจ

// Explicitly initializing the member s and v is unnecessary.
class Foo {
public:
  Foo() : s() {}

private:
  std::string s;
  std::vector<int> v {};
};

โœจ Options โœจ

IgnoreBaseInCopyConstructors

Default 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-parentheses ๐ŸŽ€

Detect redundant parentheses.

When modifying code, one often forgets to remove the corresponding parentheses. This results in overly lengthy code. When the expression is complex, finding the matching parentheses becomes particularly difficult. iykyk ๐Ÿ˜ญ

โœจ 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.

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 widely used function-like macro. For example, std::max and max macro. A workaround to distinguish them is adding parentheses around functions to prevent 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-smartptr-get

Okay bestie, this one goes hunting for all those extra .get() calls on your smart pointers and yeets them into the sun. They're doing nothing, babe. Nothing. ๐Ÿ”ฅ

Examples:

ptr.get()->Foo()  ==>  ptr->Foo()
*ptr.get()  ==>  *ptr
*ptr->get()  ==>  **ptr
if (ptr.get() == nullptr) ... => if (ptr == nullptr) ...

Options

  • IgnoreMacros โ€” if this is set to true (and yes, true is the default, she's chill like that), the check will not come for calls hiding inside macros. Leave them be. ๐Ÿ™ˆ

๐ŸŽ€ readability-redundant-string-cstr

Sniffs out unnecessary calls to std::string::c_str() and std::string::data(). Like girl, you already have the string, why are you asking for it again? ๐Ÿ˜ญ

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. Use this to configure stuff like fmt::format, spdlog::logger::info, or wrappers around those and similar functions. Default value? Empty string. Nada. โœจ

๐Ÿฆ‹ readability-redundant-string-init

Finds unnecessary string initializations. That's it, that's the vibe.

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 it to something like ::std::basic_string;llvm::StringRef;QString to run this check on your own custom classes. ๐Ÿ’–


๐Ÿ’œ readability-redundant-typename

Finds redundant uses of the typename keyword. The way that typename just shows up uninvited... not on my watch. ๐Ÿ‘‘

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 (this example shows just a few of them, iykyk):

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. Like babe, just make a new one, it's free.

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. Do it. For me. ๐Ÿ’…

๐ŸŒˆ readability-simplify-boolean-expr

Looks for boolean expressions involving boolean constants and simplifies them to use the appropriate boolean expression directly. Also simplifies boolean expressions by application of DeMorgan's Theorem. Math girlies rise up. โœจ

Examples:

  • 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

The resulting expression e is modified as follows:

  1. Unnecessary parentheses around the expression are removed.
  2. Negated applications of ! are eliminated.
  3. Negated applications of comparison operators are changed to use the opposite condition.
  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.
  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:

  1. The ternary assignment bool b = (i < 0) ? true : false; has redundant parentheses and becomes bool b = i < 0;. She's clean now. ๐Ÿงš
  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; lowkey has an implicit conversion of a pointer to bool, and it becomes return p != nullptr;. โœจ
  2. 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;
  3. 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, bestie, we just ignore boolean expressions that came from expanded macros. Default is false.
  • ChainedConditionalReturn โ€” if true, conditional boolean return statements sitting at the end of an if/else if chain get transformed. Default is false.
  • ChainedConditionalAssignment โ€” if true, conditional boolean assignments at the end of an if/else if chain get transformed too. Default is false.
  • SimplifyDeMorgan โ€” if true, DeMorgan's Theorem gets applied to simplify negated conjunctions and disjunctions. Default is true.
  • SimplifyDeMorganRelaxed โ€” if true, SimplifyDeMorgan will also transform negated conjunctions and disjunctions where there's no negation on either operand. This option literally does nothing 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. Right now it covers calling .data() and then immediately doing an array subscript operation to grab a single element โ€” in which case simply calling operator[] suffice. ๐ŸŽ€

Examples:

std::string s = ...;
char c = s.data()[i];  // char c = s[i];

๐Ÿ’… Options ๐Ÿ’…

  • Types โ€” the list of type(s) that triggers this check. Default is ::std::basic_string;::std::basic_string_view;::std::vector;::std::array;::std::span

๐Ÿฆ‹ readability-static-accessed-through-instance ๐Ÿฆ‹

Checks for member expressions that access static members through instances, and replaces them with uses of the appropriate qualified-id. No cap, it's giving "why are you going through the instance, bestie". ๐Ÿ‘‘

Example:

The following code:

struct C {
  static void foo();
  static int x;
  enum { E1 };
  enum E { E2 };
};

C *c1 = new C();
c1->foo();
c1->x;
c1->E1;
c1->E2;

is changed to:

C *c1 = new C();
C::foo();
C::x;
C::E1;
C::E2;

The --fix commandline option provides default support for safe fixes, whereas --fix-notes enables fixes that may replace expressions with side effects, potentially altering the program's behavior. ๐Ÿซถ


โญ 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.

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


๐Ÿ’œ readability-string-compare ๐Ÿ’œ

Finds string comparisons using the compare method.

A common mistake is to use the string's compare method instead of using the equality or inequality operators. The compare method is intended for sorting functions and thus returns a negative number, a positive number or zero depending on the lexicographical relationship between the strings compared. If an equality or inequality check can suffice, that is recommended. This is recommended to avoid the risk of incorrect interpretation of the return value and to simplify the code. The string equality and inequality operators can also be faster than the compare method due to early termination. ๐Ÿงš

Example

// The same rules apply to std::string_view.
std::string str1{"a"};
std::string str2{"b"};

// use str1 != str2 instead.
if (str1.compare(str2)) {
}

// use str1 == str2 instead.
if (!str1.compare(str2)) {
}

// use str1 == str2 instead.
if (str1.compare(str2) == 0) {
}

// use str1 != str2 instead.
if (str1.compare(str2) != 0) {
}

// use str1 == str2 instead.
if (0 == str1.compare(str2)) {
}

// use str1 != str2 instead.
if (0 != str1.compare(str2)) {
}

// Use str1 == "foo" instead.
if (str1.compare("foo") == 0) {
}

The above code examples show the list of if-statements that this check will give a warning for. All of them use compare to check equality or inequality of two strings instead of using the correct operators. ๐Ÿ˜ญ

Options

  • StringLikeClasses โ€” a string containing semicolon-separated names of string-like classes. By default 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 will be 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. ๐ŸŒธ


๐ŸŽ€ 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.

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 <http://en.wikipedia.org/wiki/Levenshtein_distance> describes how many single-character changes (additions, changes, or removals) must be applied to transform one string into another.

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 <http://en.wikipedia.org/wiki/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 <http://en.wikipedia.org/wiki/Sรธrensenโ€“Dice_coefficient> was originally defined to measure the similarity of two sets. Formally, the coefficient is calculated by dividing 2 * #(intersection) with #(set1) + #(set2), where #() is the cardinality function of sets. This metric is applied to strings by creating bigrams (substring sequences of length 2) of the two strings and using the set of bigrams for the two strings as the two sets. ๐Ÿงš

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 length that argument names and parameter names have to hit to even be looked at. Anything shorter than this? ignored, bestie, we don't even see it. Defaults to 3. ๐Ÿ’…
  • Abbreviations โ€” this one's for the Abbreviation heuristic (see here), and it's where you set up your abbreviations in the "abbreviation=abbreviated_value" format. It's a string, and each value gets joined up with ;.

By default, these abbreviations are already set for you, no cap:

  • addr=address
  • arr=array
  • attr=attribute
  • buf=buffer
  • cl=client
  • cnt=count
  • col=column
  • cpy=copy
  • dest=destination
  • dist=distance
  • dst=distance
  • elem=element
  • hght=height
  • i=index
  • idx=index
  • len=length
  • ln=line
  • lst=list
  • nr=number
  • num=number
  • pos=position
  • ptr=pointer
  • ref=reference
  • src=source
  • srv=server
  • stmt=statement
  • str=string
  • val=value
  • var=variable
  • vec=vector
  • wdth=width

The configuration options for each implemented heuristic (see above) are constructed dynamically. In the following, <HeuristicName> refers to one of the keys from the heuristics implemented.

  • <HeuristicName> โ€” True or False, as in whether a particular heuristic, like Equality or Levenshtein, is enabled or not. โœจ

Defaults to True for every single heuristic. she's generous like that.

  • <HeuristicName>DissimilarBelow, <HeuristicName>SimilarAbove โ€” a value between 0 and 100, expressing a percentage. These bounds set what percentage of similarity the heuristic has to deduce for the two identifiers to be considered similar or dissimilar by the check. ๐ŸŽ€

So like, given arguments arg1 and arg2 passed to param1 and param2, respectively, the bounds check goes like this: 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, then boom โ€” the swap gets reported. ๐Ÿ‘‘

For the defaults of each heuristic, see above.


Name synthesis

When it's comparing the argument names and parameter names, here's the logic it uses to gather up 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. bye bestie ๐Ÿ’€


readability-uniqueptr-delete-release

Replace delete <unique_ptr>.release() with <unique_ptr> = nullptr. The latter is shorter, simpler, and does not require use of raw pointer APIs. periodt.

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

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. ๐ŸŽ€

hicpp-uppercase-literal-suffix redirects here as an alias for this check.

So what does she do? Detects when the integral literal or floating point (decimal or hexadecimal) literal has a non-uppercase suffix and provides a fix-it hint with the uppercase suffix. all valid combinations of suffixes are supported, slay. โœจ

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.
  • 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. In C++20 mode, suggests std::ranges::any_of or std::ranges::all_of. ๐Ÿ’œ

Example:

bool all_even(std::vector<int> V) {
  for (int I : V) {
    if (I % 2)
      return false;
  }
  return true;
  // Replace loop by
  // return std::ranges::all_of(V, [](int I) { return I % 2 == 0; });
}

readability-use-concise-preprocessor-directives

Finds uses of #if that can be simplified to #ifdef or #ifndef and, since C23 and C++23, uses of #elif that can be simplified to #elifdef or #elifndef: ๐Ÿงš

#if defined(MEOW)
#if !defined(MEOW)

// becomes

#ifdef MEOW
#ifndef MEOW

Since C23 and C++23:

#elif defined(MEOW)
#elif !defined(MEOW)

// becomes

#elifdef MEOW
#elifndef MEOW

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. โš ๏ธ

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

โš ๏ธ DEPRECATED ALERT โ€” the zircon-temporary-objects check is deprecated and is getting yeeted in the 24th release of LLVM. Please use fuchsia-temporary-objects instead. Don't say I didn't warn you bestie ๐Ÿ˜ญ

okay so here's the big girl list of checks โ€” name on the left, and whether it comes with a glow-up (a fix) on the right โœจ

  • 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, just vibes ๐Ÿ™ˆ
  • abseil-no-namespace โ€” no fixes, just vibes ๐Ÿ™ˆ
  • 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, just vibes ๐Ÿ™ˆ
  • abseil-upgrade-duration-conversions โ€” offers fixes ๐Ÿ’…
  • altera-id-dependent-backward-branch โ€” no fixes, just vibes ๐Ÿ™ˆ
  • altera-kernel-name-restriction โ€” no fixes, just vibes ๐Ÿ™ˆ
  • altera-single-work-item-barrier โ€” no fixes, just vibes ๐Ÿ™ˆ
  • altera-struct-pack-align โ€” offers fixes ๐Ÿ’…
  • altera-unroll-loops โ€” no fixes, just vibes ๐Ÿ™ˆ
  • 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 ๐Ÿ’…

like, the abseil-* girlies are mostly serving fixes, the altera-* ones are lowkey just there to warn you, and the android-cloexec-* squad is fully booked with fixes fr fr ๐Ÿซถ

okay bestie, we're picking the list right back up, no skipping, no summarizing, we're doing every single row ๐Ÿ’…

quick format reminder since we're mid-table: a Yes in the trailing column means that column literally says Yes; if a row has no marker from me, that column is just empty โ€” no value, nothing, void, the silence after you send a text and they leave you on read ๐Ÿ’€

  • android-cloexec-inotify-init โ€” Yes ๐Ÿซถ cloexec but make it inotify
  • android-cloexec-inotify-init1 โ€” Yes โœจ the sequel, and yes it also gets the Yes
  • android-cloexec-memfd-create โ€” Yes ๐ŸŽ€
  • android-cloexec-open โ€” Yes ๐ŸŒธ
  • android-cloexec-pipe โ€” Yes ๐Ÿฆ‹
  • android-cloexec-pipe2 โ€” Yes ๐Ÿ”ฅ
  • android-cloexec-socket โ€” Yes ๐Ÿ’– that's the whole android-cloexec family, iconic, they all said yes
  • android-comparison-in-temp-failure-retry โ€” that column? empty, bestie, nothing there ๐Ÿ™ˆ
  • boost-use-ranges โ€” Yes ๐Ÿ‘‘
  • boost-use-to-string โ€” Yes โญ
  • bugprone-argument-comment โ€” Yes ๐Ÿ’œ
  • bugprone-assert-side-effect โ€” empty column, no notes ๐Ÿ˜ญ
  • bugprone-assignment-in-if-condition โ€” empty ๐Ÿ˜ญ (yes we know, that's the point of the check, but the column is still empty)
  • bugprone-bad-signal-to-kill-thread โ€” empty ๐Ÿซฅ
  • bugprone-bitwise-pointer-cast โ€” empty ๐ŸŒซ๏ธ
  • bugprone-bool-pointer-implicit-conversion โ€” Yes ๐ŸŒˆ
  • bugprone-branch-clone โ€” empty, twin, empty ๐Ÿ™ƒ
  • bugprone-capturing-this-in-member-variable โ€” empty ๐Ÿ‘€
  • bugprone-casting-through-void โ€” empty ๐Ÿ•ณ๏ธ
  • bugprone-chained-comparison โ€” empty โ›“๏ธ (no, the emoji isn't a claim, it's a vibe)
  • bugprone-command-processor โ€” empty ๐Ÿ’ป
  • bugprone-compare-pointer-to-member-virtual-function โ€” empty, and yes that name is a whole paragraph, we don't rename anything here ๐Ÿงš
  • bugprone-copy-constructor-init โ€” Yes ๐ŸŽ€
  • bugprone-copy-constructor-mutates-argument โ€” empty ๐Ÿ™ˆ
  • bugprone-crtp-constructor-accessibility โ€” Yes ๐Ÿ’…
  • bugprone-dangling-handle โ€” empty ๐Ÿช
  • bugprone-default-operator-new-on-overaligned-type โ€” empty, long name, empty column, balanced โœจ
  • bugprone-derived-method-shadowing-base-method โ€” empty ๐ŸŒธ
  • bugprone-dynamic-static-initializers โ€” empty ๐ŸŒ€
  • bugprone-easily-swappable-parameters โ€” empty ๐Ÿ”„
  • bugprone-empty-catch โ€” empty (the irony is not lost on me, bestie) ๐Ÿ’€
  • bugprone-exception-copy-constructor-throws โ€” empty ๐Ÿ”ฅ
  • bugprone-exception-escape โ€” empty ๐Ÿƒ
  • bugprone-float-loop-counter โ€” empty ๐Ÿ“

and the list is still going, because of course it is ๐Ÿ’œ

...and the list keeps going, bestie, we're still deep in the bugprone-* era ๐Ÿ’…

quick housekeeping so nobody's confused: in the source table every check name has a little <> after it โ€” that's just a hyperlink marker, not part of the check name, so I'm not printing it. The check names themselves are verbatim, and the ones that say Yes in the right column get a big shiny Yes here too. โœจ

  • bugprone-fold-init-type ๐ŸŽ€
  • bugprone-forward-declaration-namespace ๐ŸŽ€
  • bugprone-forwarding-reference-overload ๐ŸŽ€
  • bugprone-implicit-widening-of-multiplication-result โ€” Yes โœ…
  • bugprone-inaccurate-erase โ€” Yes โœ…
  • bugprone-inc-dec-in-conditions ๐ŸŽ€
  • bugprone-incorrect-enable-if โ€” Yes โœ…
  • bugprone-incorrect-enable-shared-from-this โ€” Yes โœ…
  • bugprone-incorrect-roundings ๐ŸŽ€
  • bugprone-infinite-loop ๐ŸŽ€
  • bugprone-integer-division ๐ŸŽ€
  • bugprone-invalid-enum-default-initialization ๐ŸŽ€
  • bugprone-lambda-function-name ๐ŸŽ€
  • bugprone-macro-parentheses โ€” Yes โœ…
  • bugprone-macro-repeated-side-effects ๐ŸŽ€
  • bugprone-misleading-setter-of-reference ๐ŸŽ€
  • bugprone-misplaced-operator-in-strlen-in-alloc โ€” Yes โœ…
  • bugprone-misplaced-pointer-arithmetic-in-alloc โ€” Yes โœ…
  • bugprone-misplaced-widening-cast ๐ŸŽ€
  • bugprone-move-forwarding-reference โ€” Yes โœ…
  • bugprone-multi-level-implicit-pointer-conversion ๐ŸŽ€
  • bugprone-multiple-new-in-one-expression ๐ŸŽ€
  • bugprone-multiple-statement-macro ๐ŸŽ€
  • bugprone-narrowing-conversions ๐ŸŽ€
  • bugprone-no-escape ๐ŸŽ€
  • bugprone-non-zero-enum-to-bool-conversion ๐ŸŽ€
  • bugprone-nondeterministic-pointer-iteration-order ๐ŸŽ€
  • bugprone-not-null-terminated-result โ€” Yes โœ…
  • bugprone-optional-value-conversion โ€” Yes โœ…
  • bugprone-parent-virtual-call โ€” Yes โœ…
  • bugprone-pointer-arithmetic-on-polymorphic-object ๐ŸŽ€
  • bugprone-posix-return โ€” Yes โœ…
  • bugprone-random-generator-seed ๐ŸŽ€
  • bugprone-raw-memory-call-on-non-trivial-type ๐ŸŽ€
  • bugprone-redundant-branch-condition โ€” Yes โœ…

not gonna lie, the <code>bugprone</code> family is long and it is thriving โ€” every single one of these is a real check name and you can hand any of them straight to <code>clang-tidy</code> as-is. no cap. ๐Ÿฆ‹

also bestie, peep how the <strong>Yes</strong> ones are scattered all cute through the list instead of clustered โ€” that's exactly how the table had them, so don't go reorganising anything. ๐Ÿ’–

we are so not done yet, keep scrolling ๐Ÿซถ

okay continuing the bugprone roster โ€” same deal as before, that second column is the little yes/no mark, so every entry below keeps its exact name and its exact mark ๐Ÿ’–

  • bugprone-reserved-identifier โ€” column says Yes โœจ she's marked.
  • bugprone-return-const-ref-from-parameter โ€” nothing in that column, no mark.
  • bugprone-shared-ptr-array-mismatch โ€” Yes ๐Ÿ’… marked, bestie.
  • bugprone-signal-handler โ€” column's empty, no mark.
  • bugprone-signed-char-misuse โ€” blank column, not marked.
  • bugprone-sizeof-container โ€” blank column, not marked.
  • bugprone-sizeof-expression โ€” blank column, not marked.
  • bugprone-spuriously-wake-up-functions โ€” blank column, not marked.
  • bugprone-standalone-empty โ€” Yes ๐ŸŽ€ marked.
  • bugprone-std-namespace-modification โ€” blank column, not marked.
  • bugprone-string-constructor โ€” Yes โœจ marked.
  • bugprone-string-integer-assignment โ€” Yes ๐Ÿ’– marked.
  • bugprone-string-literal-with-embedded-nul โ€” blank column, not marked.
  • bugprone-stringview-nullptr โ€” Yes ๐Ÿ”ฅ marked.
  • bugprone-suspicious-enum-usage โ€” blank column, not marked.
  • bugprone-suspicious-include โ€” blank column, not marked.
  • bugprone-suspicious-memory-comparison โ€” blank column, not marked.
  • bugprone-suspicious-memset-usage โ€” Yes ๐Ÿ‘‘ marked.
  • bugprone-suspicious-missing-comma โ€” blank column, not marked.
  • bugprone-suspicious-realloc-usage โ€” blank column, not marked.
  • bugprone-suspicious-semicolon โ€” Yes ๐Ÿ’€ marked.
  • bugprone-suspicious-string-compare โ€” Yes โญ marked.
  • bugprone-suspicious-stringview-data-usage โ€” blank column, not marked.
  • bugprone-swapped-arguments โ€” Yes ๐Ÿ™ˆ marked.
  • bugprone-switch-missing-default-case โ€” blank column, not marked.
  • bugprone-tagged-union-member-count โ€” blank column, not marked.
  • bugprone-terminating-continue โ€” Yes ๐ŸŒˆ marked.
  • bugprone-throw-keyword-missing โ€” blank column, not marked.
  • bugprone-throwing-static-initialization โ€” blank column, not marked.
  • bugprone-too-small-loop-variable โ€” blank column, not marked.
  • bugprone-unchecked-optional-access โ€” blank column, not marked.
  • bugprone-unchecked-string-to-number-conversion โ€” blank column, not marked.
  • bugprone-undefined-memory-manipulation โ€” blank column, not marked.
  • bugprone-undelegated-constructor โ€” blank column, not marked.
  • bugprone-unhandled-exception-at-new โ€” blank column, not marked.

and the list keeps going, so don't go anywhere ๐Ÿฆ‹

okay continuing the list, bestie, we're deep in the fixable-or-not zone now ๐Ÿซถ remember: "Yes" means clang-tidy can auto-fix it for you, and a blank means you're fixing that one with your own two hands. no cap.

  • bugprone-unhandled-self-assignment <> โ€” not marked fixable ๐Ÿ’€
  • bugprone-unintended-char-ostream-output <> โ€” Yes, fixable โœจ
  • bugprone-unique-ptr-array-mismatch <> โ€” Yes, fixable โœจ
  • bugprone-unsafe-functions <> โ€” not marked fixable
  • bugprone-unused-local-non-trivial-variable <> โ€” not marked fixable
  • bugprone-unused-raii <> โ€” Yes, fixable โœจ
  • bugprone-unused-return-value <> โ€” not marked fixable
  • bugprone-use-after-move <> โ€” not marked fixable
  • bugprone-virtual-near-miss <> โ€” Yes, fixable โœจ
  • cert-err33-c <> โ€” not marked fixable
  • cert-err60-cpp <> โ€” not marked fixable
  • cert-flp30-c <> โ€” not marked fixable
  • cert-msc50-cpp <> โ€” not marked fixable
  • cert-oop58-cpp <> โ€” not marked fixable
  • concurrency-mt-unsafe <> โ€” not marked fixable
  • concurrency-thread-canceltype-asynchronous <> โ€” not marked fixable
  • cppcoreguidelines-avoid-capturing-lambda-coroutines <> โ€” not marked fixable
  • cppcoreguidelines-avoid-const-or-ref-data-members <> โ€” not marked fixable
  • cppcoreguidelines-avoid-do-while <> โ€” not marked fixable
  • cppcoreguidelines-avoid-goto <> โ€” not marked fixable
  • cppcoreguidelines-avoid-non-const-global-variables <> โ€” not marked fixable
  • cppcoreguidelines-avoid-reference-coroutine-parameters <> โ€” not marked fixable
  • cppcoreguidelines-init-variables <> โ€” Yes, fixable โœจ
  • cppcoreguidelines-interfaces-global-init <> โ€” not marked fixable
  • cppcoreguidelines-macro-usage <> โ€” not marked fixable
  • cppcoreguidelines-misleading-capture-default-by-value <> โ€” Yes, fixable โœจ
  • cppcoreguidelines-missing-std-forward <> โ€” not marked fixable
  • cppcoreguidelines-no-malloc <> โ€” not marked fixable
  • cppcoreguidelines-no-suspend-with-lock <> โ€” not marked fixable
  • cppcoreguidelines-owning-memory <> โ€” not marked fixable
  • cppcoreguidelines-prefer-member-initializer <> โ€” Yes, fixable โœจ
  • cppcoreguidelines-pro-bounds-array-to-pointer-decay <> โ€” not marked fixable
  • cppcoreguidelines-pro-bounds-avoid-unchecked-container-access <> โ€” Yes, fixable โœจ
  • cppcoreguidelines-pro-bounds-constant-array-index <> โ€” Yes, fixable โœจ
  • cppcoreguidelines-pro-bounds-pointer-arithmetic <> โ€” not marked fixable

like, the way the cppcoreguidelines-* family is so long and it just keeps going?? main character energy fr ๐Ÿ’… and yes bestie we are still in this list, don't go anywhere ๐ŸŒธ

...and the list keeps going, bestie, we're in the cppcoreguidelines โ†’ darwin โ†’ fuchsia โ†’ google stretch of the table now ๐Ÿซถ same deal as before: name on the left, and that second column tells you whether it shows up in the default config or not. No Yes? Then it's sitting that round out. ๐Ÿ’…

  • cppcoreguidelines-pro-type-const-cast <> โ€” no Yes in its column, so it's not in the default lineup ๐ŸŽ€
  • cppcoreguidelines-pro-type-cstyle-cast <> โ€” Yes โœจ this one IS in the default set
  • cppcoreguidelines-pro-type-member-init <> โ€” Yes โœจ also default-on, she's committed
  • cppcoreguidelines-pro-type-reinterpret-cast <> โ€” blank column, no Yes for her ๐Ÿ’€
  • cppcoreguidelines-pro-type-static-cast-downcast <> โ€” Yes โœจ default-on, iconic
  • cppcoreguidelines-pro-type-union-access <> โ€” nope, no Yes here ๐Ÿ™ˆ
  • cppcoreguidelines-pro-type-vararg <> โ€” blank, not defaulted ๐ŸŒธ
  • cppcoreguidelines-rvalue-reference-param-not-moved <> โ€” no Yes, so it's off by default ๐Ÿฆ‹
  • cppcoreguidelines-slicing <> โ€” blank column, no Yes ๐Ÿ˜ญ
  • cppcoreguidelines-special-member-functions <> โ€” no Yes for this one either ๐Ÿ’œ
  • cppcoreguidelines-use-enum-class <> โ€” blank, not in the default set โญ
  • cppcoreguidelines-virtual-class-destructor <> โ€” Yes โœจ default-on queen ๐Ÿ‘‘

  • darwin-avoid-spinlock <> โ€” no Yes, so it's not on by default ๐ŸŽ€
  • darwin-dispatch-once-nonstatic <> โ€” Yes โœจ this one IS default-on

  • fuchsia-default-arguments-calls <> โ€” blank column, no Yes ๐ŸŒˆ
  • fuchsia-default-arguments-declarations <> โ€” Yes โœจ default-on, slay
  • fuchsia-overloaded-operator <> โ€” no Yes here ๐Ÿ™ˆ
  • fuchsia-statically-constructed-objects <> โ€” blank, not defaulted ๐Ÿ’…
  • fuchsia-temporary-objects <> โ€” nope, no Yes ๐Ÿฆ‹
  • fuchsia-trailing-return <> โ€” blank column, sitting this one out ๐ŸŒธ
  • fuchsia-virtual-inheritance <> โ€” no Yes, off by default ๐Ÿ’€

  • google-build-explicit-make-pair <> โ€” blank, no Yes ๐ŸŽ€
  • google-build-using-namespace <> โ€” no Yes for this one either โœจ
  • google-default-arguments <> โ€” blank column, not default-on ๐Ÿ˜ญ
  • google-explicit-constructor <> โ€” Yes โœจ default-on baddie, obsessed
  • google-global-names-in-headers <> โ€” no Yes here ๐Ÿซถ
  • google-objc-avoid-nsobject-new <> โ€” blank, not in the default lineup ๐Ÿ’œ
  • google-objc-avoid-throwing-exception <> โ€” nope, no Yes ๐Ÿ™ˆ
  • google-objc-function-naming <> โ€” blank column, off by default ๐Ÿฆ‹
  • google-objc-global-variable-declaration <> โ€” no Yes โญ
  • google-readability-avoid-underscore-in-googletest-name <> โ€” blank, not defaulted ๐ŸŒธ
  • google-readability-todo <> โ€” no Yes for this one ๐Ÿ’€
  • google-runtime-float <> โ€” blank column, sitting it out ๐ŸŽ€
  • google-runtime-int <> โ€” no Yes ๐ŸŒˆ
  • google-runtime-operator <> โ€” blank, not on by default ๐Ÿ’…

and the table just keeps unfurling, so hold my hand, we're not done yet ๐Ÿงš

  • google-upgrade-googletest-case <> โ€” flagged Yes, bestie, she's on by default โœจ
  • hicpp-exception-baseclass <> โ€” no default flag here, just vibes ๐Ÿ’œ
  • hicpp-ignored-remove-result <> โ€” not default-marked ๐ŸŒธ
  • hicpp-multiway-paths-covered <> โ€” not default-marked ๐ŸŽ€
  • hicpp-no-assembler <> โ€” not default-marked ๐Ÿฆ‹
  • hicpp-signed-bitwise <> โ€” not default-marked ๐Ÿ’…
  • linuxkernel-must-check-errs <> โ€” not default-marked ๐ŸŒˆ
  • llvm-header-guard <> โ€” not default-marked, no Yes in sight ๐Ÿ“
  • llvm-include-order <> โ€” Yes, default-on, she ate ๐Ÿ‘‘
  • llvm-namespace-comment <> โ€” not default-marked ๐Ÿงš
  • llvm-prefer-isa-or-dyn-cast-in-conditionals <> โ€” Yes, on by default ๐Ÿ’–
  • llvm-prefer-register-over-unsigned <> โ€” Yes, default-on ๐Ÿ”ฅ
  • llvm-prefer-static-over-anonymous-namespace <> โ€” not default-marked ๐Ÿ˜ญ
  • llvm-twine-local <> โ€” Yes, default-on โญ
  • llvm-use-new-mlir-op-builder <> โ€” Yes, default-on โœจ
  • llvm-use-ranges <> โ€” Yes, default-on ๐Ÿ’œ
  • llvmlibc-callee-namespace <> โ€” not default-marked ๐Ÿฆ‹
  • llvmlibc-implementation-in-namespace <> โ€” not default-marked ๐ŸŒธ
  • llvmlibc-inline-function-decl <> โ€” Yes, default-on ๐Ÿซถ
  • llvmlibc-restrict-system-libc-headers <> โ€” Yes, default-on ๐Ÿ’…
  • misc-anonymous-namespace-in-header <> โ€” not default-marked ๐ŸŽ€
  • misc-confusable-identifiers <> โ€” not default-marked ๐ŸŒˆ
  • misc-const-correctness <> โ€” Yes, default-on, main character energy ๐Ÿ‘‘
  • misc-coroutine-hostile-raii <> โ€” not default-marked ๐Ÿงš
  • misc-definitions-in-headers <> โ€” Yes, default-on ๐Ÿ’–
  • misc-header-include-cycle <> โ€” not default-marked ๐Ÿ“
  • misc-include-cleaner <> โ€” Yes, default-on, she's iconic โœจ
  • misc-misleading-bidirectional <> โ€” not default-marked ๐Ÿ˜ญ
  • misc-misleading-identifier <> โ€” not default-marked โญ
  • misc-misplaced-const <> โ€” not default-marked ๐Ÿฆ‹
  • misc-multiple-inheritance <> โ€” not default-marked ๐ŸŒธ
  • misc-new-delete-overloads <> โ€” not default-marked ๐Ÿ’œ
  • misc-no-recursion <> โ€” not default-marked ๐ŸŽ€
  • misc-non-copyable-objects <> โ€” not default-marked ๐Ÿ’…
  • misc-non-private-member-variables-in-classes <> โ€” not default-marked ๐Ÿ”ฅ
  • misc-override-with-different-visibility <> โ€” not default-marked ๐ŸŒˆ
  • misc-predictable-rand โ€” no mark in the second column ๐Ÿซฅ
  • misc-redundant-expression โ€” Yes โœจ
  • misc-static-assert โ€” Yes โœจ
  • misc-throw-by-value-catch-by-reference โ€” no mark in the second column ๐Ÿซฅ
  • misc-unconventional-assign-operator โ€” no mark in the second column ๐Ÿซฅ
  • misc-uniqueptr-reset-release โ€” Yes โœจ
  • misc-unused-alias-decls โ€” Yes โœจ
  • misc-unused-parameters โ€” Yes โœจ
  • misc-unused-using-decls โ€” Yes โœจ
  • misc-use-anonymous-namespace โ€” no mark in the second column ๐Ÿซฅ
  • misc-use-internal-linkage โ€” Yes โœจ
  • modernize-avoid-bind โ€” Yes โœจ
  • modernize-avoid-c-arrays โ€” no mark in the second column ๐Ÿซฅ
  • modernize-avoid-c-style-cast โ€” no mark in the second column ๐Ÿซฅ
  • modernize-avoid-setjmp-longjmp โ€” no mark in the second column ๐Ÿซฅ
  • modernize-avoid-variadic-functions โ€” no mark in the second column ๐Ÿซฅ
  • modernize-concat-nested-namespaces โ€” Yes โœจ
  • modernize-deprecated-headers โ€” Yes โœจ
  • modernize-deprecated-ios-base-aliases โ€” Yes โœจ
  • modernize-loop-convert โ€” Yes โœจ
  • modernize-macro-to-enum โ€” Yes โœจ
  • modernize-make-shared โ€” Yes โœจ
  • modernize-make-unique โ€” Yes โœจ
  • modernize-min-max-use-initializer-list โ€” Yes โœจ
  • modernize-pass-by-value โ€” Yes โœจ
  • modernize-raw-string-literal โ€” Yes โœจ
  • modernize-redundant-void-arg โ€” Yes โœจ
  • modernize-replace-auto-ptr โ€” Yes โœจ
  • modernize-replace-disallow-copy-and-assign-macro โ€” Yes โœจ
  • modernize-replace-random-shuffle โ€” Yes โœจ
  • modernize-return-braced-init-list โ€” Yes โœจ
  • modernize-shrink-to-fit โ€” Yes โœจ
  • modernize-type-traits โ€” Yes โœจ
  • modernize-unary-static-assert โ€” Yes โœจ
  • modernize-use-auto โ€” Yes โœจ

okay bestie so that's the misc-* girlies wrapping up and then the whole modernize-* squad rolling in, and like โ€” look at them ๐Ÿ’–. Almost every single one of those modernize- checks has a Yes sitting pretty in the second column, which is honestly main character energy of them ๐Ÿ‘‘. The misc- ones are more of a mixed bag: some got the Yes, some justโ€ฆ didn't ๐Ÿ˜ญ.

And just so we're clear on the format, since we've been living in this table for a minute: it's two columns. Column one is the check name, sitting in backticks, untouched, exactly as written. Column two is either literally Yes or it's blank โ€” and when it's blank I'm telling you it's blank, not making up a story about it. No cap ๐Ÿซถ.

The modernize- group is giving "glow up your dusty C++" โ€” modernize-avoid-bind, modernize-avoid-c-arrays, modernize-avoid-c-style-cast, modernize-avoid-setjmp-longjmp, modernize-avoid-variadic-functions all hanging out together in a little "avoid the old ways" clique ๐Ÿงš, and notice those four? no mark. Meanwhile the modernize-make-shared / modernize-make-unique / modernize-pass-by-value trio said Yes in unison, slay ๐Ÿ’….

Then the back half is just wall-to-wall Yes โ€” modernize-raw-string-literal, modernize-redundant-void-arg, modernize-replace-auto-ptr, modernize-replace-disallow-copy-and-assign-macro, modernize-replace-random-shuffle, modernize-return-braced-init-list, modernize-shrink-to-fit, modernize-type-traits, modernize-unary-static-assert, modernize-use-auto โ€” iconic, every last one of them ๐ŸŒธ.

heads up bestie: those <> bits after each check name? they're part of how the table renders, so I kept them exactly where they were. Don't @ me, I don't make the rules ๐Ÿ™ˆ.

and there's more table coming, so don't you dare close the tab ๐Ÿฆ‹

...and the list keeps going, bestie, no cap ๐Ÿซถ these are still the check names with that second column, and the second column either says Yes or it's justโ€ฆ sitting there empty. We don't make stuff up about what it means, we just report it. Here we go, still in order:

  • 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-format <> โ€” Yes ๐ŸŽ€
  • modernize-use-std-numbers <> โ€” Yes ๐Ÿฆ‹
  • modernize-use-std-print <> โ€” Yes ๐ŸŒธ
  • modernize-use-trailing-return-type <> โ€” Yes ๐Ÿ’…
  • modernize-use-transparent-functors <> โ€” Yes ๐Ÿ”ฅ
  • modernize-use-uncaught-exceptions <> โ€” Yes โญ
  • modernize-use-using <> โ€” Yes ๐Ÿ‘‘
  • mpi-buffer-deref <> โ€” Yes ๐ŸŒˆ
  • mpi-type-mismatch <> โ€” Yes ๐Ÿ’œ
  • objc-assert-equals <> โ€” Yes ๐Ÿงš
  • objc-avoid-nserror-init <> โ€” empty, that column has nothing in it ๐Ÿ™ˆ
  • objc-dealloc-in-category <> โ€” empty, that column has nothing in it ๐Ÿ’€
  • objc-forbidden-subclassing <> โ€” empty, that column has nothing in it ๐Ÿ™ˆ
  • objc-missing-hash <> โ€” empty, that column has nothing in it ๐Ÿ’€
  • objc-nsdate-formatter <> โ€” empty, that column has nothing in it ๐Ÿ™ˆ
  • objc-nsinvocation-argument-lifetime <> โ€” Yes ๐Ÿ“
  • objc-property-declaration <> โ€” Yes โœจ
  • objc-super-self <> โ€” Yes ๐Ÿ’–
  • openmp-exception-escape <> โ€” empty, that column has nothing in it ๐Ÿ’€
  • openmp-use-default-none <> โ€” empty, that column has nothing in it ๐Ÿ™ˆ
  • performance-avoid-endl <> โ€” Yes ๐ŸŽ€

okay so like. notice the pattern? the modernize- girlies are all out here with a Yes, mpi- said yes too, and then the objc- and openmp- sections are lowkey giving mixed signals โ€” some got the Yes, some got left on read with an empty cell ๐Ÿ˜ญ. That's just what the table says though, bestie, I'm not reading into it ๐Ÿ’…

and there's more coming, so don't you dare close the tab ๐Ÿซถ

  • performance-enum-size โ€” not on by default, so it's giving "opt-in only" ๐Ÿ™ˆ
  • performance-faster-string-find โ€” on by default โœจ bestie it's already watching
  • performance-for-range-copy โ€” on by default ๐Ÿฆ‹
  • performance-implicit-conversion-in-loop โ€” not on by default, silent until you say the word ๐Ÿ’œ
  • performance-inefficient-algorithm โ€” on by default ๐Ÿ”ฅ
  • performance-inefficient-string-concatenation โ€” not on by default, lowkey sleeping on you ๐Ÿ˜ญ
  • performance-inefficient-vector-operation โ€” on by default ๐Ÿ’…
  • performance-move-const-arg โ€” on by default โญ
  • performance-move-constructor-init โ€” not on by default ๐ŸŽ€
  • performance-no-automatic-move โ€” not on by default ๐ŸŒธ
  • performance-no-int-to-ptr โ€” not on by default ๐Ÿ’€
  • performance-noexcept-destructor โ€” on by default ๐Ÿ‘‘
  • performance-noexcept-move-constructor โ€” on by default ๐ŸŒˆ
  • performance-noexcept-swap โ€” on by default โœจ
  • performance-trivially-destructible โ€” on by default ๐Ÿซถ
  • performance-type-promotion-in-math-fn โ€” on by default ๐Ÿ’–
  • performance-unnecessary-copy-initialization โ€” on by default ๐Ÿ”ฅ
  • performance-unnecessary-value-param โ€” on by default ๐Ÿ’…
  • portability-avoid-pragma-once โ€” not on by default, she's resting ๐Ÿ’œ
  • portability-restrict-system-includes โ€” on by default ๐Ÿฆ‹
  • portability-simd-intrinsics โ€” not on by default ๐ŸŒธ
  • portability-std-allocator-const โ€” not on by default ๐ŸŽ€
  • portability-template-virtual-member-function โ€” not on by default ๐Ÿ™ˆ
  • readability-ambiguous-smartptr-reset-call โ€” on by default โœจ
  • readability-avoid-const-params-in-decls โ€” on by default โญ
  • readability-avoid-nested-conditional-operator โ€” not on by default, main character energy but asleep ๐Ÿ˜ญ
  • readability-avoid-return-with-void-value โ€” on by default ๐Ÿ’…
  • readability-avoid-unconditional-preprocessor-if โ€” not on by default ๐Ÿ’œ
  • readability-braces-around-statements โ€” on by default ๐Ÿ‘‘
  • readability-const-return-type โ€” on by default ๐ŸŒˆ
  • readability-container-contains โ€” on by default ๐Ÿซถ
  • readability-container-data-pointer โ€” on by default ๐Ÿ”ฅ
  • readability-container-size-empty โ€” on by default ๐Ÿ’–
  • readability-convert-member-functions-to-static โ€” on by default โœจ
  • readability-delete-null-pointer โ€” on by default ๐Ÿฆ‹

okay so that's the vibe check on this whole stretch of the list โ€” some of these are already on the second you run the thing (the "Yes" girlies, periodt ๐Ÿ’…) and the rest are just chilling off by default, waiting for you to come get them. ๐ŸŒธ

  • readability-duplicate-include โ€” Yes ๐ŸŽ€ (it's on by default, bestie)
  • readability-else-after-return โ€” Yes โœจ
  • readability-enum-initial-value โ€” Yes ๐Ÿ’–
  • readability-function-cognitive-complexity โ€” no yes here ๐Ÿ™ˆ
  • readability-function-size โ€” no yes here ๐Ÿซถ
  • readability-identifier-length โ€” no yes here ๐ŸŒธ
  • readability-identifier-naming โ€” Yes ๐Ÿ‘‘
  • readability-implicit-bool-conversion โ€” Yes ๐Ÿ”ฅ
  • readability-inconsistent-declaration-parameter-name โ€” Yes ๐Ÿ’…
  • readability-inconsistent-ifelse-braces โ€” Yes ๐Ÿฆ‹
  • readability-isolate-declaration โ€” Yes โญ
  • readability-magic-numbers โ€” no yes here ๐Ÿ˜ญ
  • readability-make-member-function-const โ€” Yes ๐Ÿ’œ
  • readability-math-missing-parentheses โ€” Yes ๐ŸŒˆ
  • readability-misleading-indentation โ€” no yes here ๐Ÿ’€
  • readability-misplaced-array-index โ€” Yes ๐Ÿ“
  • readability-named-parameter โ€” Yes ๐Ÿงš
  • readability-non-const-parameter โ€” Yes ๐ŸŽ€
  • readability-operators-representation โ€” Yes โœจ
  • readability-qualified-auto โ€” Yes ๐Ÿ’–
  • readability-redundant-access-specifiers โ€” Yes ๐Ÿ™ˆ
  • readability-redundant-casting โ€” Yes ๐Ÿซถ
  • readability-redundant-control-flow โ€” Yes ๐ŸŒธ
  • readability-redundant-declaration โ€” Yes ๐Ÿ‘‘
  • readability-redundant-function-ptr-dereference โ€” Yes ๐Ÿ”ฅ
  • readability-redundant-inline-specifier โ€” Yes ๐Ÿ’…
  • readability-redundant-member-init โ€” Yes ๐Ÿฆ‹
  • readability-redundant-parentheses โ€” Yes โญ
  • readability-redundant-preprocessor โ€” no yes here ๐Ÿ˜ญ
  • readability-redundant-smartptr-get โ€” Yes ๐Ÿ’œ
  • readability-redundant-string-cstr โ€” Yes ๐ŸŒˆ
  • readability-redundant-string-init โ€” Yes ๐Ÿ’€
  • readability-redundant-typename โ€” Yes ๐Ÿ“
  • readability-reference-to-constructed-temporary โ€” no yes here ๐Ÿงš
  • readability-simplify-boolean-expr โ€” Yes ๐ŸŽ€
  • readability-simplify-subscript-expr โ€” Yes โœจ

okay so that's the readability- girlies all lined up, and the pattern is so clear once you clock it โ€” the ones with Yes are the ones already vibing in your config, and the blank ones are the ones you gotta opt into yourself, no cap ๐Ÿ’…

  • readability-static-accessed-through-instance โ€” offers fixes: Yes โœจ
  • readability-static-definition-in-anonymous-namespace โ€” offers fixes: Yes โœจ
  • readability-string-compare โ€” offers fixes: Yes โœจ
  • readability-suspicious-call-argument โ€” no fixes offered, it just wants to talk about it ๐Ÿ’€
  • readability-uniqueptr-delete-release โ€” offers fixes: Yes โœจ
  • readability-uppercase-literal-suffix โ€” offers fixes: Yes โœจ
  • readability-use-anyofallof โ€” no fixes, she's just observing ๐Ÿซถ
  • readability-use-concise-preprocessor-directives โ€” offers fixes: Yes โœจ
  • readability-use-std-min-max โ€” offers fixes: Yes โœจ
  • zircon-temporary-objects โ€” no fixes, just judgment ๐ŸŒธ

โœจ Check aliases โœจ

Okay so some checks are like, going by a whole other name bestie. These are the aliases โ€” you say the alias, it quietly redirects you to the real check. And yes, the "offers fixes" energy carries over from whatever it's redirecting to. ๐Ÿ’…

  • cert-arr39-c โ€” redirects to bugprone-sizeof-expression. No fixes offered.
  • cert-con36-c โ€” redirects to bugprone-spuriously-wake-up-functions. No fixes offered.
  • cert-con54-cpp โ€” redirects to bugprone-spuriously-wake-up-functions. No fixes offered.
  • cert-ctr56-cpp โ€” redirects to bugprone-pointer-arithmetic-on-polymorphic-object. No fixes offered.
  • cert-dcl03-c โ€” redirects to misc-static-assert. Offers fixes: Yes โœจ
  • cert-dcl16-c โ€” redirects to readability-uppercase-literal-suffix. Offers fixes: Yes โœจ
  • cert-dcl37-c โ€” redirects to bugprone-reserved-identifier. Offers fixes: Yes โœจ
  • cert-dcl50-cpp โ€” redirects to modernize-avoid-variadic-functions. No fixes offered.
  • cert-dcl51-cpp โ€” redirects to bugprone-reserved-identifier. Offers fixes: Yes โœจ
  • cert-dcl54-cpp โ€” redirects to misc-new-delete-overloads. No fixes offered.
lowkey iconic that a whole CERT alias can just be a redirect to a bugprone check and still eat like that ๐Ÿ“

okay bestie, we're still deep in the alias table ๐Ÿ’… โ€” this next chunk is all the CERT aliases and the clang-tidy checks they secretly point at. Think of it like the group chat where every CERT rule has a glow-up name and a "government name" underneath. iykyk โœจ

  • cert-dcl58-cpp โ†’ bugprone-std-namespace-modification โ€” the alias for messing with std and getting lovingly roasted for it ๐Ÿซถ
  • cert-dcl59-cpp โ†’ misc-anonymous-namespace-in-header โ€” anonymous namespaces hiding in a header? not the vibe ๐ŸŒธ
  • cert-env33-c โ†’ bugprone-command-processor โ€” the alias for spawning stuff through the shell ๐Ÿ˜ญ
  • cert-err09-cpp โ†’ misc-throw-by-value-catch-by-reference โ€” yes bestie, that exact check ๐Ÿ’–
  • cert-err34-c โ†’ bugprone-unchecked-string-to-number-conversion โ€” strings getting yeeted into numbers with zero vibes checked ๐Ÿ”ฅ
  • cert-err52-cpp โ†’ modernize-avoid-setjmp-longjmp โ€” setjmp/longjmp catching strays, as they should ๐Ÿ‘‘
  • cert-err58-cpp โ†’ bugprone-throwing-static-initialization โ€” statics that throw at init time, iconic in the worst way ๐Ÿ’€
  • cert-err60-cpp โ†’ bugprone-exception-copy-constructor-throws โ€” a copy constructor throwing during exception handling, literally chaos โœจ
  • cert-err61-cpp โ†’ misc-throw-by-value-catch-by-reference โ€” same check as cert-err09-cpp, twins fr fr ๐ŸŽ€
  • cert-exp42-c โ†’ bugprone-suspicious-memory-comparison โ€” comparing memory and being shady about it ๐Ÿ™ˆ
  • cert-fio38-c โ†’ misc-non-copyable-objects โ€” objects that refuse to be copied, and we respect that ๐ŸŒˆ
  • cert-flp30-c โ†’ bugprone-float-loop-counter โ€” floats as loop counters, no cap that's cursed ๐Ÿ’€
  • cert-flp37-c โ†’ bugprone-suspicious-memory-comparison โ€” yep, same check as cert-exp42-c showed up again ๐Ÿฆ‹
  • cert-int09-c โ†’ readability-enum-initial-value โ€” and this one's got a lil something extra in the next column: Yes โญ
  • cert-mem57-cpp โ†’ bugprone-default-operator-new-on-overaligned-type โ€” overaligned types getting the default operator new, main character problems ๐Ÿ’œ
  • cert-msc24-c โ†’ bugprone-unsafe-functions โ€” the "please stop using the scary functions" alias ๐Ÿ“
  • cert-msc30-c โ†’ misc-predictable-rand โ€” rand() being predictable, we been knew ๐Ÿ˜ญ
  • cert-msc32-c โ†’ bugprone-random-generator-seed โ€” seeding the RNG wrong, a classic ๐Ÿงš

and yes, some of these aliases are literally the same check wearing a different outfit โ€” cert-err09-cpp and cert-err61-cpp both go to misc-throw-by-value-catch-by-reference, and cert-exp42-c and cert-flp37-c both go to bugprone-suspicious-memory-comparison. slay, honestly ๐Ÿ’…

  • cert-msc33-c <> โ€” this is the alias that lowkey points at bugprone-unsafe-functions <> ๐Ÿ’…
  • cert-msc50-cpp <> โ€” the one that's actually misc-predictable-rand <> ๐ŸŽฒ
  • cert-msc51-cpp <> โ€” bestie this one redirects you to bugprone-random-generator-seed <> โœจ
  • cert-msc54-cpp <> โ€” and here's the alias for bugprone-signal-handler <> ๐Ÿšจ
  • cert-oop11-cpp <> โ€” this alias is giving performance-move-constructor-init <> ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ
  • cert-oop54-cpp <> โ€” aka `bugprone-unhandled-self-assignment`` <> ๐Ÿ˜ญ (yes, it's really spelled that way in here, don't come for me)
  • cert-oop57-cpp <> โ€” the alias for bugprone-raw-memory-call-on-non-trivial-type <> ๐Ÿ’€
  • cert-oop58-cpp <> โ€” mapping over to bugprone-copy-constructor-mutates-argument <> ๐Ÿ™ˆ
  • cert-pos44-c <> โ€” this one's the alias for bugprone-bad-signal-to-kill-thread <> ๐Ÿ”ฅ
  • cert-pos47-c <> โ€” which is actually concurrency-thread-canceltype-asynchronous <> ๐Ÿงต
  • cert-sig30-c <> โ€” another one pointing at bugprone-signal-handler <> ๐Ÿ‘‘ (yes, same destination as cert-msc54-cpp, they're twinning)
  • cert-str34-c <> โ€” and this alias lands on bugprone-signed-char-misuse <> ๐Ÿ“

okay and now we're switching lanes into the Clang Static Analyzer aliases, which are a whole different vibe ๐Ÿ’œ

...and the list keeps GOING bestie, we are not even close to done, these static analyzer checkers are like a infinite scroll and I am here for it ๐Ÿ’…

okay periodt, that's another ten down and the table is still feeding us rows ๐Ÿซถ

okay bestie we're still deep in the checker roster โ€” no cap, this is the part where the names get very specific. Every one of these is something you can drop into your checks list, and the spelling has to be exactly right or it just won't fire. Copy-paste is your bestie here ๐Ÿซถ


and nowโ€ฆ the osx era โœจ it's giving very specific, very Apple, and honestly? iconic.

the cocoa corner really said "we have a checker for every single one of your sins" ๐Ÿ’€ and honestly, fair.



okay continuing the alias list, bestie, we're still in the clang-tidy check-alias zone ๐Ÿ’… every one of these is a check name you can totally drop into your config, each with its docs link, no skips:

and yeah that last one being a pure alias with no doc URL is giving "short and mysterious," iconic honestly ๐Ÿ’… still more list to get through, so stay with me bestie

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-avoid-magic-numbers <>                                      โ”‚ readability-magic-numbers <>                                   โ”‚              โ”‚

okay so cppcoreguidelines-avoid-magic-numbers is just readability-magic-numbers wearing a little C++ Core Guidelines costume ๐Ÿ’… same check, different name, and that third column is sitting there empty like it's too cool to participate ๐Ÿ™ˆ

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-c-copy-assignment-signature <>                              โ”‚ misc-unconventional-assign-operator <>                         โ”‚              โ”‚

next up: cppcoreguidelines-c-copy-assignment-signature โ†” misc-unconventional-assign-operator. another alias situation, third column still empty, still mysterious, still slaying in silence ๐ŸŽ€

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-explicit-virtual-functions <>                               โ”‚ modernize-use-override <>                                      โ”‚ Yes          โ”‚

Yes bestie!!! โœจ cppcoreguidelines-explicit-virtual-functions maps onto modernize-use-override and this one actually has something in that third column. the way that column finally said something ๐Ÿซถ

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-macro-to-enum <>                                            โ”‚ modernize-macro-to-enum <>                                     โ”‚ Yes          โ”‚

cppcoreguidelines-macro-to-enum โ†’ modernize-macro-to-enum, and yes, Yes again ๐Ÿ”ฅ two in a row, we're on a streak, the third column is awake now and honestly? iconic.

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-narrowing-conversions <>                                    โ”‚ bugprone-narrowing-conversions <>                              โ”‚              โ”‚

cppcoreguidelines-narrowing-conversions is the alias for bugprone-narrowing-conversions. no Yes here, back to the empty third column, it's giving mysterious again ๐Ÿ˜ญ

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-noexcept-destructor <>                                      โ”‚ performance-noexcept-destructor <>                             โ”‚ Yes          โ”‚

cppcoreguidelines-noexcept-destructor โ†’ performance-noexcept-destructor, Yes in the house ๐Ÿ’– cute little noexcept moment, and that column's filled in, we love to see it.

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-noexcept-move-operations <>                                 โ”‚ performance-noexcept-move-constructor <>                       โ”‚ Yes          โ”‚

okay so cppcoreguidelines-noexcept-move-operations points at performance-noexcept-move-constructor โ€” note bestie, "move-operations" on the left, "move-constructor" on the right, they did NOT match the wording, so don't you dare "fix" it, that's literally how it is ๐Ÿ’… and yep, Yes โญ

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-noexcept-swap <>                                            โ”‚ performance-noexcept-swap <>                                   โ”‚ Yes          โ”‚

cppcoreguidelines-noexcept-swap โ†” performance-noexcept-swap, Yes again. swap era. ๐Ÿ”„โœจ

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-non-private-member-variables-in-classes <>                  โ”‚ misc-non-private-member-variables-in-classes <>                โ”‚              โ”‚

cppcoreguidelines-non-private-member-variables-in-classes โ†’ misc-non-private-member-variables-in-classes. that's a whole mouthful, hope you stretched first ๐Ÿ“ third column empty, as is tradition for the long ones apparently.

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ cppcoreguidelines-use-default-member-init <>                                  โ”‚ modernize-use-default-member-init <>                           โ”‚ Yes          โ”‚

last of the cppcoreguidelines run: cppcoreguidelines-use-default-member-init โ†’ modernize-use-default-member-init, Yes ๐ŸŽ€ and with that, cppcoreguidelines section, we love you, goodbye ๐Ÿ‘‘


now we're moving into the fuchsia and google and hicpp neighborhoods, keep up bestie ๐Ÿฆ‹

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ fuchsia-header-anon-namespaces <>                                             โ”‚ misc-anonymous-namespace-in-header <>                          โ”‚              โ”‚

fuchsia-header-anon-namespaces โ†’ misc-anonymous-namespace-in-header. empty third column, no Yes, we move on ๐ŸŒธ

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ fuchsia-multiple-inheritance <>                                               โ”‚ misc-multiple-inheritance <>                                   โ”‚              โ”‚

fuchsia-multiple-inheritance โ†” misc-multiple-inheritance, third column blank. two fuchsia rows, two blanks, consistent at least ๐Ÿงš

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ google-build-namespaces <>                                                    โ”‚ misc-anonymous-namespace-in-header <>                          โ”‚              โ”‚

WAIT. google-build-namespaces ALSO points at misc-anonymous-namespace-in-header?? ๐Ÿ‘€ yeah bestie, that's the second time that right-hand name has shown up in this chunk โ€” fuchsia-header-anon-namespaces had it too. one target, multiple sources, it's giving popular ๐Ÿ’œ third column empty again btw.

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ google-readability-braces-around-statements <>                                โ”‚ readability-braces-around-statements <>                        โ”‚ Yes          โ”‚

google-readability-braces-around-statements โ†’ readability-braces-around-statements, and Yes โœจ braces era, we stan a curly brace, no cap.

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ google-readability-casting <>                                                 โ”‚ modernize-avoid-c-style-cast <>                                โ”‚              โ”‚

google-readability-casting โ†’ modernize-avoid-c-style-cast. note how the names do NOT echo each other here โ€” "casting" vs "avoid-c-style-cast" โ€” that's fine, that's just how the alias table rolls, don't panic ๐Ÿซถ blank third column.

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ google-readability-function-size <>                                           โ”‚ readability-function-size <>                                   โ”‚              โ”‚

google-readability-function-size โ†” readability-function-size. empty. yes bestie, even though a couple of these google ones said Yes, this one did not. mixed signals, very demure ๐Ÿ’…

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ google-readability-namespace-comments <>                                      โ”‚ llvm-namespace-comment <>                                      โ”‚              โ”‚

google-readability-namespace-comments โ†’ llvm-namespace-comment โ€” plural on the left, singular on the right, and once again we do NOT touch it, we copy it exactly ๐Ÿ™ˆ third column blank.

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ hicpp-avoid-c-arrays <>                                                       โ”‚ modernize-avoid-c-arrays <>                                    โ”‚              โ”‚

and to close out this lil batch: hicpp-avoid-c-arrays โ†’ modernize-avoid-c-arrays, third column empty, hicpp has entered the chat ๐ŸŒˆ

โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค

that's the trailing separator for this stretch, bestie โ€” and there's more table coming, so don't go anywhere ๐Ÿ’–

  • hicpp-avoid-goto โ†” cppcoreguidelines-avoid-goto โ€” third column is giving empty, bestie ๐Ÿฆ‹ no Yes here.
  • hicpp-braces-around-statements โ†” readability-braces-around-statements โ€” and the third column says Yes โœ… slay.
  • hicpp-deprecated-headers โ†” modernize-deprecated-headers โ€” third column: Yes โœ… iconic.
  • hicpp-explicit-conversions โ†” google-explicit-constructor โ€” third column: Yes โœ… periodt.
  • hicpp-function-size โ†” readability-function-size โ€” third column is empty, no cap ๐Ÿ’€.
  • hicpp-invalid-access-moved โ†” bugprone-use-after-move โ€” third column: (blank) ๐Ÿ™ˆ.
  • hicpp-member-init โ†” cppcoreguidelines-pro-type-member-init โ€” third column: Yes โœ… obsessed.
  • hicpp-move-const-arg โ†” performance-move-const-arg โ€” third column: Yes โœ… fr fr.
  • hicpp-named-parameter โ†” readability-named-parameter โ€” third column: Yes โœ… lowkey.
  • hicpp-new-delete-operators โ†” misc-new-delete-overloads โ€” third column is empty, it's giving nothing ๐Ÿซถ.
  • hicpp-no-array-decay โ†” cppcoreguidelines-pro-bounds-array-to-pointer-decay โ€” third column: (blank) ๐ŸŒธ.
  • hicpp-no-malloc โ†” cppcoreguidelines-no-malloc โ€” third column is empty, bestie ๐Ÿงš.
  • hicpp-noexcept-move โ†” performance-noexcept-move-constructor โ€” third column: Yes โœ… ate.
  • hicpp-special-member-functions โ†” cppcoreguidelines-special-member-functions โ€” third column: (blank) ๐Ÿ’….
  • hicpp-static-assert โ†” misc-static-assert โ€” third column: Yes โœ… main character energy.
  • hicpp-undelegated-constructor โ†” bugprone-undelegated-constructor โ€” third column is empty, no Yes ๐Ÿ˜ญ.
  • hicpp-uppercase-literal-suffix โ†” readability-uppercase-literal-suffix โ€” third column: Yes โœ… ๐Ÿ”ฅ.
  • hicpp-use-auto โ†” modernize-use-auto โ€” third column: Yes โœ… ๐Ÿ’–.
  • hicpp-use-emplace โ†’ modernize-use-emplace โ€” Yes ๐Ÿ’–
  • hicpp-use-equals-default โ†’ modernize-use-equals-default โ€” Yes โœจ
  • hicpp-use-equals-delete โ†’ modernize-use-equals-delete โ€” Yes ๐ŸŽ€
  • hicpp-use-noexcept โ†’ modernize-use-noexcept โ€” Yes ๐Ÿฆ‹
  • hicpp-use-nullptr โ†’ modernize-use-nullptr โ€” Yes ๐ŸŒธ
  • hicpp-use-override โ†’ modernize-use-override โ€” Yes ๐Ÿ’…
  • hicpp-vararg โ†’ cppcoreguidelines-pro-type-vararg โ€” (no โ€œYesโ€ here, just the mapping) ๐Ÿซถ
  • llvm-else-after-return โ†’ readability-else-after-return โ€” Yes ๐Ÿ˜ญ
  • llvm-qualified-auto โ†’ readability-qualified-auto โ€” Yes ๐Ÿ”ฅ

and thatโ€™s the bottom of the alias table, bestie. periodt. ๐Ÿ’€


๐Ÿฆ‹ Query Based Custom Clang-Tidy Checks ๐Ÿฆ‹

โœจ Introduction โœจ

okay so this page? itโ€™s literally a whole tutorial on how to add query based custom checks for clang-tidy. so slay. โœจ

Custom checks are based on clang-query syntax. Every custom check gets registered in a custom module to avoid name conflict, bestie. They can be enabled or disabled by the checks option just like the built-in checks. ๐Ÿ’–

Custom checks support inheritance from parent configurations like other configuration items. ๐Ÿ‘‘

Goals: easy to write, cross platform, multiple versions supported toolkit for custom clang-tidy rules. Non-Goals: complex checks, performance, fix-its, etc. so like, keep it cute and simple. ๐ŸŽ€

๐Ÿ’… Configuration ๐Ÿ’…

CustomChecks is a list of custom checks. Each check must contain:

  • Name: check name can be used in -checks option.
  • Query: clang-query string
  • Diagnostic: list of diagnostics to be reported.
    • 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, Warning.

CustomChecks can be configured by 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
int main(); // note: main function.

void bar() {
  main(); // warning: call to main function. [custom-call-main-function]
}

๐Ÿšจ Matters Need Attention ๐Ÿšจ

okay so this is important, bestie: this feature needs to be explicitly enabled by --experimental-custom-checks because it is currently in the experimental stage. ๐Ÿšจ Welcome to submit any suggestions in the link <https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331>. ๐Ÿ™ˆ

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

In subsequent versions, including non-experimental stage, the query statements will change at any time. The essence of clang-query is to parse the query string and dynamically generate the corresponding AST matcher. Therefore, its functionality is entirely dependent on the functions provided by the AST matcher library. The ast matcher will change along with the changes in the clang AST. Please refer to ast matcher reference <https://clang.llvm.org/docs/LibASTMatchersReference.html>. ๐Ÿ’€


๐Ÿซถ Clang-tidy IDE/Editor Integrations ๐Ÿซถ

Apart from being a standalone tool, clang-tidy is integrated into various IDEs, code analyzers, and editors. We recommend using clangd <http://clangd.llvm.org/> which integrates clang-tidy and is available <https://clangd.llvm.org/installation.html#editor-plugins> in most major editors through plugins (Vim, Emacs, Visual Studio Code, Sublime Text and more <https://langserver.org/#implementations-client>). ๐Ÿฆ‹

The following table shows the most well-known clang-tidy integrations in detail.

hereโ€™s the vibe check on the tools, bestie. The table has a Feature column group, and the columns are: Tool, On-the-fly inspection, Check list configuration (GUI), Options to checks (GUI), Configuration via .clang-tidy files, Custom clang-tidy binary. ๐Ÿ’…

  • 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      โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Clangd      โ”‚ +          โ”‚ -             โ”‚ -          โ”‚ +             โ”‚ -          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ CLion IDE   โ”‚ +          โ”‚ +             โ”‚ +          โ”‚ +             โ”‚ +          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ CodeChecker โ”‚ -          โ”‚ -             โ”‚ -          โ”‚ -             โ”‚ +          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ CPPCheck    โ”‚ -          โ”‚ -             โ”‚ -          โ”‚ -             โ”‚ -          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ CPPDepend   โ”‚ -          โ”‚ -             โ”‚ -          โ”‚ -             โ”‚ -          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Flycheck    โ”‚ +          โ”‚ -             โ”‚ -          โ”‚ +             โ”‚ +          โ”‚
โ”‚ for Emacs   โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ KDevelop    โ”‚ -          โ”‚ +             โ”‚ +          โ”‚ +             โ”‚ +          โ”‚
โ”‚ IDE         โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Qt Creator  โ”‚ +          โ”‚ +             โ”‚ -          โ”‚ +             โ”‚ +          โ”‚
โ”‚ IDE         โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ReSharper   โ”‚ +          โ”‚ +             โ”‚ -          โ”‚ +             โ”‚ +          โ”‚
โ”‚ C++ for     โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”‚ Visual      โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”‚ Studio      โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Syntastic   โ”‚ +          โ”‚ -             โ”‚ -          โ”‚ -             โ”‚ +          โ”‚
โ”‚ for Vim     โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Visual      โ”‚ +          โ”‚ +             โ”‚ -          โ”‚ -             โ”‚ -          โ”‚
โ”‚ Assist for  โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”‚ Visual      โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ”‚ Studio      โ”‚            โ”‚               โ”‚            โ”‚               โ”‚            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ’ป IDEs

CLion <https://www.jetbrains.com/clion/> 2017.2 and later literally just absorbs clang-tidy <https://www.jetbrains.com/help/clion/clang-tidy-checks-support.html> as an extension to its built-in code analyzer. bestie. it's giving "we got tired of writing our own checks so we borrowed yours." ๐Ÿ’…

and starting from 2018.2 EAP, CLion lets you use clang-tidy via Clangd. Inspections and applicable quick-fixes happen on the fly โœจ, and checks can be configured in standard command line format. In this integration you get to:

  • 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

KDevelop <https://www.kdevelop.org/> with the kdev-clang-tidy <https://github.com/KDE/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. no cap, that's the whole job. ๐ŸŽ€

QtCreator <https://www.qt.io/> 4.6 integrates clang-tidy warnings into the editor diagnostics under the Clang Code Model <https://doc.qt.io/qtcreator/creator-clang-codemodel.html>. To employ clang-tidy inspection in QtCreator you need to create a copy of one of the presets and choose the checks to be performed. Since QtCreator 4.7 project-wide analysis is possible with the Clang Tools <https://doc.qt.io/qtcreator/creator-clang-tools.html> analyzer. ๐ŸŒธ

MS Visual Studio <https://visualstudio.microsoft.com/> can integrate clang-tidy by means of three different tools. okay three whole tools, pick your fighter ๐Ÿ‘‘

๐Ÿ“ Editors

Emacs24 <https://www.gnu.org/s/emacs/>, when expanded with the Flycheck <https://github.com/ch1bo/flycheck-clang-tidy> plugin, incorporates the clang-tidy inspection into the syntax analyzer. โœจ

For Vim <https://www.vim.org/>, you can use Syntastic <https://github.com/vim-syntastic/syntastic>, which includes clang-tidy, or A.L.E. <https://github.com/w0rp/ale>, a lint engine that applies clang-tidy along with other linters. vim girlies we got you ๐Ÿฆ‹

๐Ÿ”ฌ Analyzers

clang-tidy is integrated in CPPDepend <https://www.cppdepend.com/cppdependv2018> starting from version 2018.1 and CPPCheck <https://sourceforge.net/p/cppcheck/news/> 1.82. The CPPCheck integration lets you import Visual Studio solutions and run the clang-tidy inspection on them.

The CodeChecker <https://github.com/Ericsson/codechecker> application of version 5.3 or later โ€” which also comes as a plugin <https://github.com/Ericsson/CodeCheckerEclipsePlugin> for Eclipse โ€” supports clang-tidy as a static analysis instrument and allows you to use a custom clang-tidy binary. ๐Ÿ”ฅ


๐Ÿซถ Getting Involved

clang-tidy has several own checks and can run Clang static analyzer checks, but its power is in the ability to easily write custom checks. periodt. ๐Ÿ’…

Checks are organized in modules, which can be linked into clang-tidy with minimal or no code changes in clang-tidy. ๐ŸŽ€

Checks can plug into the analysis on the preprocessor level using PPCallbacks <https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html> or on the AST level using AST Matchers <https://clang.llvm.org/docs/LibASTMatchers.html>. When an error is found, checks can report them in a way similar to how Clang diagnostics work. A fix-it hint can be attached to a diagnostic message. โœจ

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. bestie you literally could be the one ๐Ÿ‘‘

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 <https://clang.llvm.org/docs/ClangCheck.html> with the -ast-dump (and optionally -ast-dump-filter) provides a convenient way to dump AST of a C++ program.
โš ๏ธ heads up bestie: if CMake is configured with CLANG_TIDY_ENABLE_STATIC_ANALYZER=NO, clang-tidy will not be built with support for the clang-analyzer-* checks or the mpi-* checks. ๐Ÿ˜ญ
โš ๏ธ and 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

If you have an idea of a check, you should decide whether it should be implemented as a:

  • Clang diagnostic โ€” if your check is generic enough, goes after code patterns that are most probably actual bugs (not style, not readability vibes), can be implemented effectively and with an extremely low false-positive rate, then bestie it might just make a gorgeous Clang diagnostic ๐Ÿ’…
  • Clang static analyzer check โ€” if the check needs any kind of control flow analysis, it should probably be a static analyzer check. Periodt.
  • clang-tidy check</clang> โ€” this is the moment for linter-style checks, checks tied to a certain coding style, checks that address code readability, etc. It's giving linter core ๐ŸŽ€

๐Ÿ› ๏ธ Preparing your Workspace ๐Ÿ› ๏ธ

If you're new to LLVM development, go read these first, no skipping bestie:

These will walk you through checking out and building LLVM, Clang and Clang Extra Tools with CMake. Slay โœจ

Once you're done, cd into the llvm/clang-tools-extra directory, and let's start! ๐ŸŒธ

When you configure the CMake build (https://llvm.org/docs/GettingStarted.html#local-llvm-configuration), make sure you enable the clang and clang-tools-extra projects so clang-tidy actually builds. And because your new check is gonna have associated documentation (iconic behaviour tbh), you'll also want to install Sphinx (https://www.sphinx-doc.org/en/master/) and enable it in the CMake configuration. To save build time on the core Clang libraries, you may want to only enable the X86 target in the CMake configuration. Trust, your laptop will thank you ๐Ÿ˜ญ


๐Ÿ“ The Directory Structure ๐Ÿ“

clang-tidy source code lives in the llvm/clang-tools-extra directory and is structured like this, look:

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
|-- ObjCModuleTest.cpp
    ...

โœ๏ธ Writing a clang-tidy Check โœ๏ธ

So you have an idea for a useful check for clang-tidy. Obsessed already ๐Ÿซถ

First things first: if you're not familiar with LLVM development, read through the Getting Started with the LLVM System document (https://llvm.org/docs/GettingStarted.html) for instructions on setting up your workflow, and the LLVM Coding Standards document (https://llvm.org/docs/CodingStandards.html) so you can get familiar with the coding style used in the project. For code reviews, we currently use LLVM Github (https://github.com/llvm/llvm-project), though historically we used Phabricator. Yes, the lore goes back ๐Ÿ’€

Next, you need to decide which module the check belongs to. Modules are located in subdirectories of clang-tidy/ (https://github.com/llvm/llvm-project/tree/main/clang-tools-extra/clang-tidy/) and they 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 ๐ŸŒˆ

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, fr fr.

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 wanted 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. Non-negotiable 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 at the preprocessor level instead, we'd need to override the registerPPCallbacks method.

In the registerMatchers method, we create an AST Matcher (see AST Matchers at https://clang.llvm.org/docs/LibASTMatchers.html for more info) 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 at https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.h and clang-tidy/google/ExplicitConstructorCheck.cpp at https://reviews.llvm.org/diffusion/L/browse/clang-tools-extra/trunk/clang-tidy/google/ExplicitConstructorCheck.cpp.)

If you need to interact with macros or preprocessor directives, you will want to override the method registerPPCallbacks. Heads up though: the add_new_check.py script does not generate an override for this method in the starting point for your new check. You're on your own for that one ๐Ÿ™ˆ


๐Ÿ’ก Check development tips ๐Ÿ’ก

Writing your first check can be a daunting task, particularly if you are unfamiliar with the LLVM and Clang code bases. Here are some suggestions for orienting yourself in the codebase and working on your check incrementally. Baby steps bestie, you got this ๐ŸŒŸ


๐Ÿ“š Guide to useful documentation ๐Ÿ“š

Many of the support classes created for LLVM are used by Clang, such as StringRef (https://llvm.org/docs/ProgrammersManual.html#the-stringref-class) and SmallVector (https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h). These and other commonly used classes are described in the Important and useful LLVM APIs (https://llvm.org/docs/ProgrammersManual.html#important-and-useful-llvm-apis) and Picking the Right Data Structure for the Task (https://llvm.org/docs/ProgrammersManual.html#picking-the-right-data-structure-for-a-task) sections of the LLVM Programmer's Manual (https://llvm.org/docs/ProgrammersManual.html). You don't need to memorize all the details of these classes; the generated doxygen documentation (https://llvm.org/doxygen/) has everything if you need it. And in the header LLVM/ADT/STLExtras.h (https://llvm.org/doxygen/STLExtras_8h.html) you'll find useful versions of the STL algorithms that operate on LLVM containers, such as llvm::all_of (https://llvm.org/doxygen/STLExtras_8h.html#func-members). Iconic library design, honestly ๐Ÿ‘‘

Clang is implemented on top of LLVM and introduces its own set of classes that you will interact with while writing your check. When a check issues diagnostics and fix-its, these are associated with locations in the source code. Source code locations, source files, ranges of source locations and the SourceManager (https://clang.llvm.org/doxygen/classclang_1_1SourceManager.html) class provide the mechanisms for describing such locations. These and other topics are described in the "Clang" CFE Internals Manual (https://clang.llvm.org/docs/InternalsManual.html). Whereas the doxygen generated documentation serves as a reference to the internals of Clang, this document serves as a guide to other developers. Topics in that manual of interest to a check developer are:

Most checks will interact with C++ source code via the AST. Some checks will interact with the preprocessor. The input source file is lexed and preprocessed and then parsed into the AST. Once the AST is fully constructed, the check is run by applying the check's registered AST matchers against the AST and invoking the check with the set of matched nodes from the AST. Monitoring the actions of the preprocessor is detached from the AST construction, but a check can collect information during preprocessing for later use by the check when nodes are matched by the AST. The whole pipeline is lowkey beautiful once you see it ๐Ÿฆ‹

Every syntactic (and sometimes semantic) lil element of your C++ source code gets its own class in the AST, bestie ๐Ÿ’… You pick the parts of the AST you actually care about by composing AST matcher functions together. And fr fr, you're gonna wanna sit down and study the AST Matcher Reference (<https://clang.llvm.org/docs/LibASTMatchersReference.html>) so you understand how all these matcher functions relate to each other. It's giving homework but it's giving necessary homework ๐Ÿซถ

โœจ Using the Transformer library โœจ

The Transformer library lets you write a check that literally transforms source code by expressing the transformation as a RewriteRule. It hands you functions for composing edits to source code so you can build those rewrite rules. Unless you're out here doing low-level source location manipulation (main character energy, respect), you probs wanna write your check with the Transformer library instead. The Clang Transformer Tutorial (<https://clang.llvm.org/docs/ClangTransformerTutorial.html>) explains the whole thing in detail.

To use the Transformer library, make these changes to the code that add_new_check.py generated for you:

  • Include ../utils/TransformerClangTidyCheck.h instead of ../ClangTidyCheck.h ๐ŸŽ€
  • Switch 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 into TransformerClangTidyCheck's constructor

๐ŸŒธ Developing your check incrementally ๐ŸŒธ

The absolute bestie move for developing your check is to start with simple test cases and add complexity incrementally. The test file that add_new_check.py creates is your starting point. Here's a rough outline of the whole process:

  • 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 so the diagnostics and fix-its get validated
  • Build the check-clang-tools target to confirm the test passes โœ…
  • Repeat until every aspect of your check is covered by tests

The quickest way to prototype your matcher is to use clang-query and interactively build that matcher up. For the complicated ones, build a matching expression incrementally and use clang-query's let command to save named matching expressions, which simplifies your matcher so much ๐Ÿ’–

clang-query> let c1 cxxRecordDecl()
clang-query> match c1

Alternatively, pressing the tab key after a previous matcher's open parens would also show you which matchers can be chained with the previous matcher โ€” though some matchers that do work might not be listed. And heads up: tab completion does not currently work on Windows ๐Ÿ™ˆ

Just like breaking a huge function into smaller chunks with intention-revealing names helps you understand a complex algorithm, breaking a matcher into smaller matchers with intention-revealing names helps you understand a complicated matcher. It's the same energy, no cap.

Once you have a working clang-query matcher, the C++ API matchers will be the same or similar to your interactively constructed matcher (there can be cases where they differ slightly, so don't panic ๐Ÿ˜ญ). You can use local variables to preserve those intention-revealing names you gave to 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. That's your moment โ€” you can create your own private matcher using the exact same infrastructure as the public matchers. A private matcher can simplify the processing in your check method by eliminating complex hand-crafted AST traversal of the matched nodes. Using 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. Iconic behavior honestly ๐Ÿ‘‘

๐Ÿ’œ Unit testing helper code ๐Ÿ’œ

Private custom matchers are a perfect example of auxiliary support code for your check that you can test with a unit test. Writing a unit test is way easier than writing a FileCheck integration test when it comes to testing your matchers or other support classes. The ASTMatchersTests target contains unit tests for the public AST matcher classes, and it's a great source of testing idioms for matchers ๐Ÿ“

You can build the Clang-Tidy unit tests by building the ClangTidyTests target. Quick warning though: test targets in LLVM and Clang are excluded from the "build all" style action of IDE-based CMake generators, so you need to explicitly build the target for the unit tests to actually get built. Sneaky, ik.

๐Ÿ’… Making your check robust ๐Ÿ’…

Once you've covered your check with the basic "happy path" scenarios, you're gonna wanna torture that check with as many edge cases as you can possibly cover, to make sure it's actually robust. Running your check on a large code base, like Clang/LLVM, is a great way to catch things you forgot to account for in your matchers. But ngl, the LLVM code base may be insufficient for testing purposes since it was developed against a particular set of coding styles and quality measures. The larger the corpus of code your check gets tested against, the more confidence the community will have in the check's efficacy and 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 need to do this test manually until automated support for checking messages and fix-its is added to the check_clang_tidy.py script ๐Ÿ˜ญ
  • Define macros that contain code matched by your check
  • Define template classes that contain code matched by your check
  • Define template specializations that contain code matched by your check
  • Test your check under both Windows and Linux environments
  • Watch out for high false-positive rates. Ideally a check would have zero false positives, but given that matching against an AST is not control- or data flow-sensitive, a number of false positives are expected. The higher the false-positive rate, the less likely the check gets adopted in practice. Mechanisms should be put in place to help the user manage false positives ๐Ÿซถ
  • There are two primary mechanisms for managing false positives: supporting a code pattern which lets the programmer silence the diagnostic in an ad hoc manner, and check configuration options to control the behavior of the check
  • Consider supporting a code pattern that lets the programmer silence the diagnostic whenever such a code pattern can clearly express the programmer's intent. For example, allowing an explicit cast to void to silence an unused variable diagnostic
  • Consider adding check configuration options to let the user opt into more aggressive checking behavior, without burdening users for the common high-confidence cases

๐ŸŒˆ Documenting your check ๐ŸŒˆ

The add_new_check.py script creates entries in the release notes (<https://clang.llvm.org/extra/ReleaseNotes.html>), the list of checks, and a whole new file for the check documentation itself. It's recommended that you have a concise summary of what your check does in a single sentence, repeated in the release notes, as the first sentence in the doxygen comments in the header file for your check class, and as the first sentence of the check documentation. Oh, and avoid the phrase "this check" in your check summary and check documentation โ€” just don't do it, bestie ๐Ÿ’€

If your check relates to a published coding guideline (C++ Core Guidelines, SEI CERT, etc.) or a style guide, provide links to the relevant guideline or style guide sections in your check documentation.

Provide enough examples of the diagnostics and fix-its your check provides so that a user can easily understand what will happen to their code when the check is run. If there are exceptions or limitations to your check, document them thoroughly โ€” this helps users understand the scope of the diagnostics and fix-its your check provides.

Building the docs-clang-tools-html target 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 your check is correctly shown in the release notes and the list of checks. Make sure the formatting and structure of your check's documentation look correct: there is no trailing whitespaces and lines are no longer than 80 characters. Yes, 80. Count them. ๐Ÿ“

To validate your files, please use doc8 as described below.

Clang-Tidy uses doc8 (<https://pypi.org/project/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. But if you wanna create a new module, or you just 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 the LLVM build system, we need this lil hack to make sure the module actually gets 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 add this to the main translation unit of the clang-tidy binary (or the binary you link the clang-tidy library into), 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 โœจ

okay so bestie, if your check needs config options, it can grab its very own check-specific ones with the Options.get<Type>("SomeOption", DefaultValue) call, right there in the check constructor. ๐Ÿ’–

But wait, there's more โ€” your check should also override the ClangTidyCheck::storeOptions method so the options it provides become discoverable. This method is literally how clang-tidy learns which options your check implements and what their current values are (think: 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);
  }
  ...

So let's say your check got registered with the name "my-check" โ€” then you can set that option in a .clang-tidy file like this:

CheckOptions:
  my-check.SomeOption1: 123
  my-check.SomeOption2: 'some other value'

And if you're the kind of icon who needs to specify check options straight on the command line? Slay, use the inline YAML format: ๐Ÿ’…

$ clang-tidy -config="{CheckOptions: {a: b, x: y}}" ...

๐Ÿงช Testing Checks ๐Ÿงช

To run the tests for clang-tidy, build the check-clang-tools target. Like, if you set up your CMake build with the ninja project generator, run:

$ ninja check-clang-tools

clang-tidy checks can be tested with either unit tests or lit tests (see https://llvm.org/docs/CommandGuide/lit.html). Unit tests might be more convenient when you're testing complex replacements with strict checks, no cap. Lit tests let you use partial text matching and regular expressions, which makes them way better for writing compact tests for diagnostic messages. โœจ

The check_clang_tidy.py script gives you 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 using two separate FileCheck invocations (see https://llvm.org/docs/CommandGuide/FileCheck.html): 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). ๐Ÿ‘‘

Specifically, CHECK-FIXES: can be used to check that code was not modified by fix-its โ€” you do it by checking that it's 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 all you need for clang-tidy tests. Heads up though: the FileCheck documentation mostly assumes the default prefix (CHECK), so it describes the directive as CHECK:, CHECK-SAME:, CHECK-NOT:, etc. You just swap CHECK for either CHECK-FIXES or CHECK-MESSAGES for clang-tidy tests. ๐Ÿฆ‹

There's also an additional check enabled by check_clang_tidy.py that makes sure that if CHECK-MESSAGES: is used in a file, then every warning or error must have an associated CHECK in that file. Or, if you want to also ensure that all the notes are checked, you can use CHECK-NOTES: instead. ๐Ÿ˜Œ

To use the check_clang_tidy.py script, drop a .cpp file with the appropriate RUN line in the test/clang-tidy directory. Then write checks against diagnostic messages and fixed code using CHECK-MESSAGES: and CHECK-FIXES: lines. ๐ŸŽ€

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. fr fr, don't skip this one.

Here's an example of a test using the check_clang_tidy.py script (the full source code lives at test/clang-tidy/checkers/google/readability-casting.cpp, a.k.a. https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/test/clang-tidy/checkers/google/readability-casting.cpp):

// RUN: %check_clang_tidy %s google-readability-casting %t

void f(int a) {
  int b = (int)a;
  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant cast to the same type [google-readability-casting]
  // CHECK-FIXES: int b = a;
}

Wanna check more than one scenario in the same test file? Use -check-suffix=SUFFIX-NAME on the check_clang_tidy.py command line, or -check-suffixes=SUFFIX-NAME-1,SUFFIX-NAME-2,.... With -check-suffix[es]=SUFFIX-NAME you need to replace your CHECK-* directives with CHECK-MESSAGES-SUFFIX-NAME and CHECK-FIXES-SUFFIX-NAME. ๐Ÿ’–

Example time:

// RUN: %check_clang_tidy -check-suffix=USING-A %s misc-unused-using-decls %t -- -- -DUSING_A
// RUN: %check_clang_tidy -check-suffix=USING-B %s misc-unused-using-decls %t -- -- -DUSING_B
// RUN: %check_clang_tidy %s misc-unused-using-decls %t
...
// CHECK-MESSAGES-USING-A: :[[@LINE-8]]:10: warning: using decl 'A' {{.*}}
// CHECK-MESSAGES-USING-B: :[[@LINE-7]]:10: warning: using decl 'B' {{.*}}
// CHECK-MESSAGES: :[[@LINE-6]]:10: warning: using decl 'C' {{.*}}
// CHECK-FIXES-USING-A-NOT: using a::A;$
// CHECK-FIXES-USING-B-NOT: using a::B;$
// CHECK-FIXES-NOT: using a::C;$

There are so many dark corners in the C++ language, and making your check work perfectly in all cases can be genuinely difficult, 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's 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. ๐Ÿงš

And if you need to validate how your check interacts with system header files, there's a set of simulated system header files sitting 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 you submit a pull request, contributors are encouraged to run clang-tidy and clang-format on their changes to ensure code quality and catch potential issues. clang-tidy is not currently enforced in CI, but 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
Heads up: 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 ๐Ÿ”Œ

Developing an out-of-tree check as a plugin mostly follows the same steps outlined above โ€” including creating a new module and doing the hacks to register the module. The plugin is a shared library whose code lives outside the clang-tidy build system. Build and link this shared library against LLVM exactly like you'd do for other kinds of Clang plugins. If you're using CMake, use the keyword MODULE when invoking add_library or llvm_add_library. โœจ

You can load the plugin by passing -load to clang-tidy, plus the names of the checks to enable:

$ clang-tidy --checks=-*,my-explicit-constructor -list-checks -load myplugin.so

There are no expectations regarding ABI and API stability, so the plugin must be compiled against the version of clang-tidy that will be loading the plugin. no cap, get this one right. ๐Ÿ˜ญ

The plugins can use threads, TLS, or any other facilities available to in-tree code which is accessible from the external headers. ๐Ÿ‘‘

Note that testing out-of-tree checks might involve getting llvm-lit from an LLVM installation compiled from source. See Getting Started with the LLVM System (https://llvm.org/docs/GettingStarted.html) for ways to do so.

Alternatively, get lit (https://llvm.org/docs/CommandGuide/lit.html) following the test-suite guide (https://llvm.org/docs/TestSuiteGuide.html) and get the FileCheck binary (https://llvm.org/docs/CommandGuide/FileCheck.html), and write a version of check_clang_tidy.py (https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/test/clang-tidy/check_clang_tidy.py) to suit your needs. ๐Ÿฆ‹


๐ŸŒˆ Running clang-tidy on LLVM ๐ŸŒˆ

To test a check, it's best to try it out on a larger code base. LLVM and Clang are the natural targets since you already have the source code lying around. The most convenient way to run clang-tidy is with a compile command database; CMake can automatically generate one; for a description of how to enable it, see How To Setup Clang Tooling For LLVM (https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html). Once compile_commands.json is in place and a working version of clang-tidy is in PATH, the entire code base can be analyzed with clang-tidy/tool/run-clang-tidy.py. The script executes clang-tidy with the default set of checks on every translation unit in the compile command database and displays the resulting warnings and errors. The script provides multiple configuration flags. ๐Ÿ’–

  • The default set of checks can be overridden using the -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, pass -fix as an argument. It gathers all the changes in a temporary directory and applies them. And if you pass -format too, it runs clang-format over the changed lines. ๐Ÿ’…

๐Ÿฆ‹ On checks profiling ๐Ÿฆ‹

okay so bestie, clang-tidy can collect per-check profiling info, and it'll output it for each processed source file (that's a translation unit, iykyk).

To turn on profiling info collection, use the -enable-check-profile argument. The timings get output to stderr as a table. Example output:

$ clang-tidy -enable-check-profile -checks=-*,readability-function-size source.cpp
===-------------------------------------------------------------------------===
                          clang-tidy checks profiling
===-------------------------------------------------------------------------===
  Total Execution Time: 1.0282 seconds (1.0258 wall clock)

   ---User Time---   --System Time--   --User+System--   ---Wall Time---  --- Name ---
   0.9136 (100.0%)   0.1146 (100.0%)   1.0282 (100.0%)   1.0258 (100.0%)  readability-function-size
   0.9136 (100.0%)   0.1146 (100.0%)   1.0282 (100.0%)   1.0258 (100.0%)  Total

It can also store that data as JSON files for further processing. Example output:

$ clang-tidy -enable-check-profile -store-check-profile=. -checks=-*,readability-function-size source.cpp
$ # Note that there won't be timings table printed to the console.
$ ls /tmp/out/
20180516161318717446360-source.cpp.json
$ cat 20180516161318717446360-source.cpp.json
{
"file": "/path/to/source.cpp",
"timestamp": "2018-05-16 16:13:18.717446360",
"profile": {
  "time.clang-tidy.readability-function-size.wall": 1.0421266555786133e+00,
  "time.clang-tidy.readability-function-size.user": 9.2088400000005421e-01,
  "time.clang-tidy.readability-function-size.sys": 1.2418899999999974e-01
}
}

There is only one argument that controls profile storage:

  • -store-check-profile=<prefix>

By default, reports are printed in tabulated format to stderr. When you pass this option, those per-TU profiles get stored as JSON instead. If the prefix is not an absolute path, it's 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 is giving 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 the necessary things, such as how to write the CMakeLists.txt for an out-of-tree plugin of clang-tidy checks. ๐Ÿ› ๏ธ

If you know of (or wrote!) a tool or project using clang-tidy, please share it on the Discourse forums (Clang Frontend category) https://discourse.llvm.org/c/clang/6 for wider visibility, and open a pull-request on LLVM Github https://github.com/llvm/llvm-project to have it added here. Since the primary purpose of this page is to provide examples that can help developers, the listed projects should have code available.

Since clang-tidy is using, for example, the AST Matchers and diagnostics of Clang, External Clang Examples https://clang.llvm.org/docs/ExternalClangExamples.html may also be useful to look at for such examples. ๐Ÿ‘‘

List of projects and tools

clang-tidy is a clang-based C++ "linter" tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis. clang-tidy is modular and provides a convenient interface for writing new checks. โœจ


๐ŸŒธ Using Clang-Tidy ๐ŸŒธ

clang-tidy is a LibTooling https://clang.llvm.org/docs/LibTooling.html-based tool, and it's easier to work with if you set up a compile command database for your project (for an example of how to do this, see How To Setup Tooling For LLVM https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html). You can also specify compilation options on the command line after --:

$ clang-tidy test.cpp -- -Imy_project/include -DMY_DEFINES ...

If there are too many options or source files to specify on the command line, you can store them in a parameter file, and use clang-tidy with that parameters file:

$ clang-tidy @parameters_file

clang-tidy has its own checks and can also run Clang Static Analyzer checks. Each check has a name and the checks to run can be chosen using the -checks= option, which specifies a comma-separated list of positive and negative (prefixed with -) globs. Positive globs add subsets of checks, and negative globs remove them. For example,

$ clang-tidy test.cpp -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus*

will disable all default checks (-*) and enable all clang-analyzer-* checks except for clang-analyzer-cplusplus* ones. ๐Ÿ’–

The -list-checks option lists all the enabled checks. When used without -checks=, it shows checks enabled by default. Use -checks=* to see all available checks or with any other value of -checks= to see which checks are enabled by this value. ๐Ÿ”ฅ

so rn there are literally these groups of checks, bestie โ€” each one is a name prefix and what it's about ๐Ÿ’…

  • abseil- โ€” Checks related to Abseil library.
  • altera- โ€” Checks related to OpenCL programming for FPGAs.
  • android- โ€” Checks related to Android.
  • boost- โ€” Checks related to Boost library.
  • bugprone- โ€” Checks that target bug-prone code constructs.
  • cert- โ€” Checks related to CERT Secure Coding Guidelines.
  • clang-analyzer- โ€” Clang Static Analyzer checks.
  • concurrency- โ€” Checks related to concurrent programming (including threads, fibers, coroutines, etc.).
  • cppcoreguidelines- โ€” Checks related to C++ Core Guidelines.
  • darwin- โ€” Checks related to Darwin coding conventions.
  • fuchsia- โ€” Checks related to Fuchsia coding conventions.
  • google- โ€” Checks related to Google coding conventions.
  • hicpp- โ€” Checks related to High Integrity C++ Coding Standard.
  • linuxkernel- โ€” Checks related to the Linux Kernel coding conventions.
  • llvm- โ€” Checks related to the LLVM coding conventions.
  • llvmlibc- โ€” Checks related to the LLVM-libc coding standards.
  • misc- โ€” Checks that we didn't have a better category for.
  • modernize- โ€” Checks that advocate usage of modern (currently "modern" means "C++11") language constructs.
  • mpi- โ€” Checks related to MPI (Message Passing Interface).
  • objc- โ€” Checks related to Objective-C coding conventions.
  • openmp- โ€” Checks related to OpenMP API.
  • performance- โ€” Checks that target performance-related issues.
  • portability- โ€” Checks that target portability-related issues that don't relate to any particular coding style.
  • readability- โ€” Checks that target readability-related issues that don't relate to any particular coding style.
  • zircon- โ€” Checks related to Zircon kernel coding conventions.

okay so Clang diagnostics get treated in a similar way as check diagnostics โœจ Clang diagnostics are displayed by clang-tidy and can be filtered out using the -checks= option. BUT โ€” and this is the plot twist ๐Ÿซ  โ€” 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. no cap.

And the -warnings-as-errors= option upgrades any warnings emitted under the -checks= flag to errors โ€” but (!!) it does not enable any checks itself. it's giving "promotion, not hiring" ๐Ÿ˜ญ

Clang diagnostics have check names starting with clang-diagnostic-. Diagnostics which have a corresponding warning option are named clang-diagnostic-<warning-option> โ€” like, Clang warning controlled by -Wliteral-conversion will be reported with check name clang-diagnostic-literal-conversion. iconic naming convention, honestly ๐Ÿ‘‘

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. so likeโ€ฆ it's the main character and you cannot vote it off the island ๐Ÿ’€

The -fix flag instructs clang-tidy to fix found errors if supported by corresponding checks. yes queen, auto-fix era ๐Ÿ› ๏ธโœจ


An overview of all the command-line options:

$ clang-tidy --help
USAGE: clang-tidy [options] <source0> [... <sourceN>]

OPTIONS:

Generic Options:

  --help                           - Display available options (--help-hidden for more)
  --help-list                      - Display list of available options (--help-list-hidden for more)
  --version                        - Display the version of this program

clang-tidy options:
  • --checks=<string> โ€” okay so this is a comma-separated list of globs, and each one can have an optional '-' prefix ๐Ÿ’… 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, and globs with the '-' prefix remove checks with matching names from the set of enabled checks. Also โ€” plot twist โ€” this option's value is appended to the value of the 'Checks' option in .clang-tidy, if there is one. ๐Ÿซถ
  • --config=<string> โ€” specifies a configuration in YAML/JSON format, like so:
-config="{Checks: '*',
          CheckOptions: {x: y}}"

And 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 the --config option after reading the specified config file. Use either --config-file or --config, not both โ€” pick a lane bestie ๐Ÿ˜ญ
  • --dump-config โ€” dumps configuration in the YAML format to stdout. This option can be used along with a file name (and '--' if the file is outside of a project with configured compilation database). The configuration used for this file will be printed. Use it along with -checks=* to include configuration of all checks. ๐Ÿ‘‘
  • --enable-check-profile โ€” enables per-check timing profiles, and prints a report to stderr. โฑ๏ธ
  • --enable-module-headers-parsing โ€” enables preprocessor-level module header parsing for C++20 and above, empowering specific checks to detect macro definitions within modules. > Heads up though: this feature may cause performance and parsing issues and is therefore considered experimental. ๐Ÿงช
  • --exclude-header-filter=<string> โ€” a regular expression matching the names of the headers to exclude diagnostics from. Diagnostics from the main file of each translation unit are always displayed, no matter what. Must be used together with --header-filter. Can be used together with -line-filter. This option overrides the 'ExcludeHeaderFilterRegex' option in .clang-tidy file, if any. ๐Ÿšซ
  • --explain-config โ€” for each enabled check, explains where it's enabled, i.e. in clang-tidy binary, command line, or a specific configuration file. it's giving detective energy ๐Ÿ•ต๏ธ
  • --export-fixes=<filename> โ€” a YAML file to store suggested fixes in. The stored fixes can be applied to the input source code with clang-apply-replacements. ๐Ÿ› ๏ธ
  • --extra-arg=<string> โ€” additional argument to append to the compiler command line.
  • --extra-arg-before=<string> โ€” additional argument to prepend to the compiler command line. โœจ
  • --fix โ€” apply suggested fixes. Without -fix-errors, clang-tidy will bail out if any compilation errors were found. ๐Ÿ™ˆ
  • --fix-errors โ€” apply suggested fixes even if compilation errors were found. If compiler errors have attached fix-its, clang-tidy will apply them as well. ๐Ÿ”ฅ
  • --fix-notes โ€” if a warning has no fix, but a single fix can be found through an associated diagnostic note, apply the fix. Specifying this flag will implicitly enable the '--fix' flag. ๐Ÿ’ก
  • --format-style=<string> โ€” style for formatting code around applied fixes:
    • 'none' (default) turns off formatting
    • 'file' (literally 'file', not a placeholder) uses .clang-format file in the closest parent directory
    • '{ <json> }' specifies options inline, e.g. -format-style='{BasedOnStyle: llvm, IndentWidth: 8}'
    • 'llvm', 'google', 'webkit', 'mozilla'

See clang-format documentation for the up-to-date information about formatting styles and options. This option overrides the 'FormatStyle option in .clang-tidy file, if any. ๐ŸŽ€

  • --header-filter=<string> โ€” a 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 .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'. ๐Ÿ’…

  • --list-checks โ€” list all enabled checks and exit. Use with -checks=* to list all available checks. ๐Ÿ“‹
  • --load=<pluginfilename> โ€” load the specified plugin. ๐Ÿ”Œ
  • -p <string> โ€” build path.
  • --quiet โ€” run clang-tidy in quiet mode. This suppresses printing statistics about ignored warnings and warnings treated as errors if the respective options are specified. ๐Ÿคซ
  • --store-check-profile=<prefix> โ€” by default reports are printed in tabulated format to stderr. When this option is passed, these per-TU profiles are instead stored as JSON. ๐Ÿ“Š
  • --system-headers โ€” display the errors from system headers. This option overrides the 'SystemHeaders' option in .clang-tidy file, if any. ๐Ÿ–ฅ๏ธ
  • --use-color โ€” use colors in diagnostics. If not set, colors will be used if the terminal connected to standard output supports colors. This option overrides the 'UseColor' option in .clang-tidy file, if any. ๐ŸŒˆ
  • --verify-config โ€” check the config files to ensure each check and option is recognized. โœ…
  • --vfsoverlay=<filename> โ€” overlay the virtual filesystem described by file over the real file system. ๐Ÿ—‚๏ธ
  • --warnings-as-errors=<string> โ€” upgrades warnings to errors. Same format as '-checks'. This option's value is appended to the value of the 'WarningsAsErrors' option in .clang-tidy file, if any. ๐Ÿ˜ค
  • --allow-no-checks โ€” allow empty enabled checks. This suppresses the "no checks enabled" error when disabling all of the checks. ๐Ÿซถ

-p <build-path> is used to read a compile command database. ๐Ÿ“š

For example, it can be a CMake build directory in which a file named compile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON CMake option to get this output). When no build path is specified, a search for compile_commands.json will be attempted through all parent paths of the first input file. See: https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an example of setting up Clang Tooling on a source tree. ๐ŸŒณ

lowkey iconic that it just walks up your directories hunting for that json bestie, no cap ๐Ÿ”โœจ
  • <source0> ... โ€” bestie, this is where you tell clang-tidy the paths of source files โœจ. These paths get looked up in the compile command database. If a path is absolute, it needs to point into CMake's source tree. If it's relative, your current working directory needs to be in the CMake source tree and the file has to be in a subdirectory of your current working directory. Those ./ prefixes in relative files get automatically removed (slay ๐Ÿ’…), but the rest of a relative path has to be a suffix of a path in the compile command database. no cap.

๐ŸŒธ Parameters files ๐ŸŒธ

A large number of options or source files can be passed as parameter files by using @parameter-file on the command line. cute. ๐ŸŽ€

๐ŸŒธ Configuration files ๐ŸŒธ

clang-tidy attempts to read configuration for each source file from a .clang-tidy file located in the closest parent directory of the source file. The .clang-tidy file is specified in YAML format. If any configuration options have a corresponding command-line option, the command-line option takes precedence. periodt. ๐Ÿ‘‘

The following configuration options may be used in a .clang-tidy file:

  • CheckOptions โ€” list of key-value pairs defining check-specific options. Example:
CheckOptions:
  some-check.SomeOption: 'some value'
  • Checks โ€” same as --checks. Bonus: the list of globs can be specified as a list instead of a string. โœจ
  • CustomChecks โ€” list 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. obsessed. ๐Ÿฆ‹
  • SystemHeaders โ€” same as --system-headers.
  • UseColor โ€” same as --use-color.
  • User โ€” specifies the name or e-mail of the user running clang-tidy. This option is used, for example, to place the correct user name in TODO() comments in the relevant check.
  • WarningsAsErrors โ€” same as --warnings-as-errors.
  • RemovedArgs โ€” same as --removed-arg

The effective configuration can be inspected using --dump-config:

$ clang-tidy --dump-config
---
Checks:              '-*,some-check'
WarningsAsErrors:    ''
HeaderFileExtensions:         ['', 'h','hh','hpp','hxx']
ImplementationFileExtensions: ['c','cc','cpp','cxx']
HeaderFilterRegex:   '.*'
FormatStyle:         none
InheritParentConfig: true
User:                user
CheckOptions:
  some-check.SomeOption: 'some value'
...

โœจ Clang-Tidy Automation โœจ

clang-tidy can analyze multiple source files by specifying them on the command line. For larger projects, automation scripts provide additional functionality like parallel execution and integration with version control systems. main character energy fr 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. ๐Ÿš€

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 <https://github.com/rizsotto/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 lets you run clang-tidy on the lines that have been modified in your working directory or in a specific diff. IMPORTANT: 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. fr fr. ๐Ÿ˜ญ This is particularly useful for code reviews and continuous integration, because it focuses analysis on the changed code rather than the entire codebase. ๐Ÿ’…

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.

โš ๏ธ bestie warning: 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 <checks/readability/function-size.html>), 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.

๐Ÿคซ 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 <checks/bugprone/use-after-move.html> can be silenced by re-initializing the variable after it has been moved out, bugprone-string-integer-assignment <checks/bugprone/string-integer-assignment.html> can be suppressed by explicitly casting the integer to char, readability-implicit-bool-conversion <checks/readability/implicit-bool-conversion.html> can also be suppressed by using explicit casts, etc. ๐Ÿ’…

If a specific suppression mechanism is not available for a certain warning, or its use is not desired for some reason, clang-tidy has a generic mechanism to suppress diagnostics using NOLINT, NOLINTNEXTLINE, and NOLINTBEGIN ... NOLINTEND comments. ๐ŸŽ€

The NOLINT comment instructs clang-tidy to ignore warnings on the same line (it doesn't apply to a function, a block of code or any other language construct; it applies to the line of code it is on). If introducing the comment on the same line would change the formatting in an undesired way, the NOLINTNEXTLINE comment allows suppressing clang-tidy warnings on the next line. The NOLINTBEGIN and NOLINTEND comments allow suppressing clang-tidy warnings on multiple lines (affecting all lines between the two comments). ๐Ÿฆ‹

All comments can be followed by an optional list of check names in parentheses (see below for the formal syntax). The list of check names supports globbing, with the same format and semantics as for enabling checks. Note: negative globs are ignored here, as they would effectively re-activate the warning. ๐Ÿ‘‘

For example:

class Foo {
  // Suppress all the diagnostics for the line
  Foo(int param); // NOLINT

  // Consider explaining the motivation to suppress the warning
  Foo(char param); // NOLINT: Allow implicit conversion from `char`, because <some valid reason>

  // Silence only the specified checks for the line
  Foo(double param); // NOLINT(google-explicit-constructor, google-runtime-int)

  // Silence all checks from the `google` module
  Foo(bool param); // NOLINT(google*)

  // Silence all checks ending with `-avoid-c-arrays`
  int array[10]; // NOLINT(*-avoid-c-arrays)

  // Silence only the specified diagnostics for the next line
  // NOLINTNEXTLINE(google-explicit-constructor, google-runtime-int)
  Foo(bool param);

  // Silence all checks from the `google` module for the next line
  // NOLINTNEXTLINE(google*)
  Foo(bool param);

  // Silence all checks ending with `-avoid-c-arrays` for the next line
  // NOLINTNEXTLINE(*-avoid-c-arrays)
  int array[10];

Okay bestie, the example keeps going โ€” here's the rest of that little NOLINT showcase, and then we get into the formal grammar because of course we do ๐Ÿ’…

            // 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 โœจ

Bestie, this is literally the grammar. Not a vibe, an actual grammar. Here it is:

          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

Okay so the tea: whitespaces between NOLINT / NOLINTNEXTLINE / NOLINTBEGIN / NOLINTEND and the opening parenthesis are NOT allowed. If you sneak one in, the comment just gets treated as a plain NOLINT / NOLINTNEXTLINE / NOLINTBEGIN / NOLINTEND with no args at all. ๐Ÿ™ˆ But inside the check names list โ€” the stuff in the parentheses โ€” whitespaces are totally fine and get ignored. โœจ

And another thing, and this one is non-negotiable: every single NOLINTBEGIN comment must be paired with an equal number of NOLINTEND comments. Not vibes-based. Actual count. ๐Ÿซถ

Plus the pairs have to have matching arguments. So like, NOLINTBEGIN(check-name) can be paired with NOLINTEND(check-name) but not with NOLINTEND (that's zero arguments, bestie, they don't match). If any NOLINTBEGIN/NOLINTEND comment breaks these rules, clang-tidy will hit you with a clang-tidy-nolint error diagnostic. ๐Ÿ’€


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

Okay so real talk: one of the biggest annoyances of C++ compared to basically every other language is that you have to manage your #include directives by hand, in every file, forever. ๐Ÿ˜ญ clang-include-fixer addresses one aspect of this problem: it gives you an automated way of adding #include directives for missing symbols in one translation unit. Iconic behaviour, honestly. ๐Ÿ‘‘

And while it's inserting those missing #includes, clang-include-fixer also adds missing namespace qualifiers to all instances of an unidentified symbol โ€” but only if the symbol is missing some prefix namespace qualifiers. Little bonus glow-up. โœจ

๐ŸŽ€ Setup ๐ŸŽ€

To use clang-include-fixer you need two databases. Both can be generated with tools you already have, no cap.

  • Compilation database. Contains the compiler commands for any given file in a project, and can be generated by CMake โ€” see How To Setup Tooling For LLVM at https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html.
  • Symbol index. Contains all symbol information in a project, so it can 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 clang-include-fixer can automatically pick them up if you call it with a source file from that tree. ๐Ÿซถ

Heads up though: 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 ships with find-all-symbols, a tool that creates a symbol database in YAML format from a compilation database, by parsing all the source files listed in it. The commands below show how to set up a database for LLVM โ€” and honestly, any project built by CMake should follow pretty much the same 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 ๐Ÿ’œ

Want to run clang-include-fixer on a potentially unsaved buffer in Vim? Say less. Just add this 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 it's bound to backslash by default. (There's a whole tutorial on it at http://vim.wikia.com/wiki/Mapping_keys_in_Vim_-_Tutorial_(Part_3)#Map_leader if you're curious, bestie.) ๐Ÿงš

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 searches 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, first make sure Emacs can find clang-include-fixer.el by adding the directory containing the file to the load-path, and then require 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. And if there's more than one header that would define the symbol, you get prompted to pick 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. So whenever Clang tries to correct a potential typo, it emits a callback to the include fixer, which then looks for a corresponding file. And at that point, rich lookup information is still available โ€” info that is not available in the AST at a later stage. ๐Ÿง โœจ

The identifier that should be typo corrected is then sent to the database. If a header file is returned, it gets 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 you want multiple #include additions, just rerun the program until you reach a fix-point. Repeat until slay. ๐Ÿ’…

๐Ÿฆ‹ CLANG-CHANGE-NAMESPACE ๐Ÿฆ‹

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 you call when you want to move a class/function definition into a whole new surrounding namespace. Glow-up for your codebase fr fr.

So here's the tea ๐Ÿ’… โ€” classes/functions sitting in the moved namespace get new namespaces, while references to symbols (like types, functions) that are not defined in the changed namespace get properly qualified, by prepending namespace specifiers right before them. And bestie, it tries to add the shortest namespace specifiers possible. Efficiency queen. โœจ

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 stay chillin' in the old namespace. This will be demonstrated in the next example. ๐Ÿ‘‘

๐ŸŒธ Example usage ๐ŸŒธ

Okay so consider this test.cc example, with the forward declared class FWD and the defined class A, both living in 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 namespace a. It wasn't moved to the new namespace because it wasn't defined/declared here in a but only forward declared. It's giving "I was just a promise, not the real thing". ๐Ÿ™ˆ

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. Main character energy, no cap. ๐ŸŽ€

๐ŸŒˆ Caveats ๐ŸŒˆ

Content already exists in new namespace

Consider this test.cc example that defines two class A, one inside 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 two definitions of class A inside 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. Not the vibe you want at 2am, bestie. ๐Ÿ˜ญ

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;
}
๐Ÿ’€ One cannot use clang-change-namespace to inline a namespace. Periodt.

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. The betrayal. ๐Ÿ™ˆ

Only symbol references in the moved namespace are updated, not outside of it. Iykyk.


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

๐ŸŒŸ 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.

<include-files-list> specifies the path of a file name for a file containing the newline-separated list of headers to check with respect to each other. Lines beginning with '#' and empty lines are ignored. Header file names followed by a colon and other space-separated file names will include those extra files as dependencies. The file names can be relative or full paths, but must be on the same line. For example:

header1.h
header2.h
header3.h: header1.h header2.h

Note that unless a -prefix (header path) option is specified, non-absolute file paths in the header list file will be relative to the header list file directory. Use -prefix to specify a different directory.

<front-end-options> is a place-holder for regular Clang front-end arguments, which must follow the <include-files-list>. Note that by default, modularize assumes .h files contain C++ source, so if you are using a different language, you might need to use a -x option to tell Clang that the header contains another language, i.e.: -x c

Note also that because modularize does not use the clang driver, you will likely need to pass in additional compiler front-end arguments to match those passed in by default by the driver. She's independent like that. ๐Ÿ’โ™€๏ธ


๐ŸŽ€ Modularize Command Line Options ๐ŸŽ€

  • -prefix=<header-path> โ€” Prepend the given path to non-absolute file paths in the header list file. By default, headers are assumed to be relative to the header list file directory. Use -prefix to specify a different directory.
  • -module-map-path=<module-map-path> โ€” Generate a module map and output it to the given file. See the description in Module Map Generation.
  • -problem-files-list=<problem-files-list-file-name> โ€” For use only with module map assistant. Input list of files that have problems with respect to modules. These will still be included in the generated module map, but will be marked as "excluded" headers.
  • -root-module=<root-name> โ€” Put modules generated by the -module-map-path option in an enclosing module with the given name. See the description in Module Map Generation.
  • -block-check-header-list-only โ€” Limit the #include-inside-extern-or-namespace-block check to only those headers explicitly listed in the header list. This is a work-around for avoiding error messages for private includes that purposefully get included inside blocks.
  • -no-coverage-check โ€” Don't do the coverage check for a module map.
  • -coverage-check-only โ€” Only do the coverage check for a module map.
  • -display-file-lists โ€” this one's a little โœจgiftโœจ: it prints out a list of the good files (aka the ones with no compile errors, iconic behavior honestly), the problem files, and a combined list where every problem file is prefixed with a '#'. It's the fastest way to eyeball which of your files are being a problem ๐Ÿ’€. And bestie, that combined list is literally the cheat code for starting to modularize a whole set of headers: start with your full header list, run -display-file-lists, grab the combined list, use it as your intermediate list, and just uncomment headers one by one as you fix them. Chef's kiss ๐ŸŽ€.

๐Ÿ’– modularize, the icon itself

modularize is a standalone tool that vibe-checks whether a set of headers provides the consistent definitions required to use modules. Periodt. For example, it detects whether the same entity (say, a NULL macro or a size_t typedef) is defined in multiple headers, or whether a header produces different definitions under different circumstances. These conditions make modules built from those headers behave poorly (not slay at all), and they should be fixed before you go introducing a module map.

modularize also has an assistant mode option for generating a module map file based on the provided header list. The generated file is a functional module map that can be used as a starting point for a module.modulemap file. Yes bestie, it does your homework for you ๐Ÿซถ.


๐ŸŒธ Getting Started

To build from source:

  1. Read Getting Started with the LLVM System at https://llvm.org/docs/GettingStarted.html and Clang Tools Documentation at https://clang.llvm.org/docs/ClangTools.html for the info on getting sources for LLVM, Clang, and Clang Extra Tools.
  2. Getting Started with the LLVM System at https://llvm.org/docs/GettingStarted.html and Building LLVM with CMake at https://llvm.org/docs/CMake.html give directions for how to build. With sources all checked out into the right place, the LLVM build will build Clang Extra Tools and their dependencies automatically. automatic queen behavior ๐Ÿ‘‘
    • If you're using CMake, you can also use the modularize target to build just the modularize tool and its dependencies. lowkey so convenient.

Before you keep going, take a look at Modularize Usage to see how to invoke modularize. Don't skip it bestie, iykyk.


โœจ 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 message 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 will collect the names of all the files it considers headers (no extension, .h, or .inc โ€” and if you need more, bestie, go modify the isHeader function yourself ๐Ÿ’…). It then 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 will be produced, and this program will return an error code of 1. If no problems are found, an error code of 0 is returned. clean, no drama โœจ.

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, these can be included on the command line after the module map file argument.

Warning message have the form:

   warning: module.modulemap does not account for file: Level3A.h

Note that for the case of the module map referencing a file that does not exist, the module map parser in Clang will (at the time of this writing) display an error message. โš ๏ธ not our problem, that's Clang's.

To limit the checks modularize does to just the module map coverage check, use the -coverage-check-only option. The way that it just does the one thing? obsessed.

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 will be created for each header. Also, if the header in the header list is a partial path, a nested module hierarchy will be created in which a module will be 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 will be enclosed in these same modules also. nesting? we love to see it ๐Ÿช†.

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. a little mother module holding all her babies ๐Ÿฅน.

An optional -problem-files-list=<problem-file-name> can be used to input a list of files to be excluded, perhaps as a temporary stop-gap measure until problem headers can be fixed. (no judgement, we've all been there ๐Ÿ™ˆ)

For example, with the same header list from above:

   // Output/NoProblemsAssistant.txt
   // Generated by: modularize -module-map-path=Output/NoProblemsAssistant.txt \
        -root-module=Root NoProblemsAssistant.modularize

   module Root {
     module SomeTypes {
       header "SomeTypes.h"
       export *
     }
     module SomeDecls {
       header "SomeDecls.h"
       export *
     }
     module SubModule1 {
       module Header1 {
         header "SubModule1/Header1.h"
         export *
       }
       module Header2 {
         header "SubModule1/Header2.h"
         export *
       }
     }
     module SubModule2 {
       module Header3 {
         header "SubModule2/Header3.h"
         export *
       }
       module Header4 {
         header "SubModule2/Header4.h"
         export *
       }
       header "SubModule2.h"
       export *
     }
   }
Note that headers with dependents will be ignored with a warning, as the Clang module mechanism doesn't support headers that rely on other headers being included first. sad but true ๐Ÿ˜ญ

The module map format defines some keywords which can't be used in module names. If a header has one of these names, an underscore ('_') will be prepended to the name. For example, if the header name is header.h, because header is a keyword, the module name will be _header. For a list of the module map keywords, please see: Lexical structure at https://clang.llvm.org/docs/Modules.html#lexical-structure ๐Ÿงš

โœจ PP-TRACE USER'S MANUAL โœจ

okay bestie, meet pp-trace ๐Ÿ’– โ€” a standalone tool that traces preprocessor activity. it's also literally used as a test of Clang's PPCallbacks interface, so it's pulling double duty and slaying both jobs. ๐Ÿ‘‘

here's the vibe: it runs a given source file through the Clang preprocessor, displaying selected info from callback functions overridden in a PPCallbacks derivation (https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html). the output is in a high-level YAML format, described in pp-trace Output Format down below. ๐Ÿฆ‹

๐ŸŽ€ pp-trace Usage ๐ŸŽ€

Command Line Format

pp-trace [<pp-trace-options>] <source-file> [-- <front-end-options>]

so <pp-trace-options> is just a lil place-holder for options that are specific to pp-trace, and those are described below in Command Line Options. โœจ

<source-file> = the source file you wanna run through the preprocessor. no cap. ๐Ÿ“„

<front-end-options> is a place-holder for regular Clang Compiler Options (https://clang.llvm.org/docs/UsersManual.html#command-line-options) โ€” and heads up, they must follow the <source-file>. like, order matters bestie. ๐Ÿ˜ญ

Command Line Options

  • -callbacks <comma-separated-globs> โ€” this one lets you specify a comma-separated list of globs describing the list of callbacks that should be traced. globs get processed in order of appearance: positive globs add matched callbacks to the set, and negative globs (the ones with the - prefix) remove callacks from the set. ๐Ÿซถ
    • FileChanged
    • FileSkipped
    • InclusionDirective
    • moduleImport
    • EndOfMainFile
    • Ident
    • PragmaDirective
    • PragmaComment
    • PragmaDetectMismatch
    • PragmaDebug
    • PragmaMessage
    • PragmaDiagnosticPush
    • PragmaDiagnosticPop
    • PragmaDiagnostic
    • PragmaOpenCLExtension
    • PragmaWarning
    • PragmaWarningPush
    • PragmaWarningPop
    • MacroExpands
    • MacroDefined
    • MacroUndefined
    • Defined
    • SourceRangeSkipped
    • If
    • Elif
    • Ifdef
    • Ifndef
    • Else
    • Endif
  • -output <output-file> โ€” by default, pp-trace outputs the trace information to stdout. use this option to output the trace information to a file instead. ๐Ÿ’…

๐ŸŒˆ pp-trace Output Format ๐ŸŒˆ

the pp-trace output is formatted as YAML. see https://yaml.org/ for general YAML info, iykyk. it's arranged as a sequence of information about the callback call, including the callback name and argument information, for example:

---
- Callback: Name
  Argument1: Value1
  Argument2: Value2
(etc.)
...

with real data:

---
- Callback: FileChanged
  Loc: "c:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-include.cpp:1:1"
  Reason: EnterFile
  FileType: C_User
  PrevFID: (invalid)
  (etc.)
- Callback: FileChanged
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-include.cpp:5:1"
  Reason: ExitFile
  FileType: C_User
  PrevFID: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/Input/Level1B.h"
- Callback: EndOfMainFile
...

in all but one case (MacroDirective), the "Argument" scalars have the same name as the argument in the corresponding PPCallbacks callback function. โœจ


๐Ÿ’œ Callback Details ๐Ÿ’œ

the following sections describe the purpose and output format for each callback. ๐Ÿ“–

click on the callback name in the section heading to see the Doxygen documentation for the callback. ๐Ÿ‘†

the argument descriptions table describes the callback argument information displayed.

the Argument Name field in most (but not all) cases is the same name as the callback function parameter. ๐Ÿซถ

the Argument Value Syntax field describes the values that will be displayed for the argument value. it uses an ad hoc representation that mixes literal and symbolic representations. enumeration member symbols are shown as the actual enum member in a (member1|member2|...) form. a name in parentheses can either represent a place holder for the described value, or confusingly, it might be a literal, such as (null), for a null pointer. locations are shown as quoted only to avoid confusing the documentation generator. ๐Ÿ˜…

the Clang C++ Type field is the type from the callback function declaration.

the description describes the argument or what is displayed for it.

note that in some cases, such as when a structure pointer is an argument value, only some key member or members are shown to represent the value, instead of trying to display all members of the structure. don't come for it, it's just how it is. ๐Ÿ’€

FileChanged Callback

https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a7cc8cfaf34114fc65e92af621cd6464e

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. the range, honestly. ๐ŸŒธ

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value                                     โ”‚ Clang C++ Type                โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                                             โ”‚                               โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)"                              โ”‚ SourceLocation                โ”‚ The location of โ”‚
โ”‚               โ”‚                                                    โ”‚                               โ”‚ the directive.  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Reason        โ”‚ (EnterFile|ExitFile|SystemHeaderPragma|RenameFile) โ”‚ PPCallbacks::FileChangeReason โ”‚ Reason for      โ”‚
โ”‚               โ”‚                                                    โ”‚                               โ”‚ change.         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ FileType      โ”‚ (C_User|C_System|C_ExternCSystem)                  โ”‚ SrcMgr::CharacteristicKind    โ”‚ Include type.   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ PrevFID       โ”‚ ((file)|(invalid))                                 โ”‚ 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

https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ab5b338a0670188eb05fa7685bbfb5128

FileSkipped is called when a source file is skipped as the result of header guard optimization. iconic behavior honestly. ๐Ÿงš

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value                    โ”‚ Clang C++ Type             โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                            โ”‚                            โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ParentFile    โ”‚ ("(file)" or                      โ”‚ const FileEntry            โ”‚ The file that   โ”‚
โ”‚               โ”‚ (null))                           โ”‚                            โ”‚ #included the   โ”‚
โ”‚               โ”‚                                   โ”‚                            โ”‚ skipped file.   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ FilenameTok   โ”‚ (token)                           โ”‚ const Token                โ”‚ The token in    โ”‚
โ”‚               โ”‚                                   โ”‚                            โ”‚ ParentFile that โ”‚
โ”‚               โ”‚                                   โ”‚                            โ”‚ indicates the   โ”‚
โ”‚               โ”‚                                   โ”‚                            โ”‚ skipped file.   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ FileType      โ”‚ (C_User|C_System|C_ExternCSystem) โ”‚ SrcMgr::CharacteristicKind โ”‚ The file type.  โ”‚

โ€ฆaaand that's the whole FileSkipped table, bestie ๐ŸŽ€ so here's what it actually looks like in the wild:

- Callback: FileSkipped
  ParentFile: "/path/filename.h"
  FilenameTok: "filename.h"
  FileType: C_User

๐Ÿ’… InclusionDirective Callback

Doc link, for the girlies who like to read source: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a557d9738c329793513a6f57d6b60de52

InclusionDirective gets called when an inclusion directive of any kind (#include, #import, etc.) has been processed โ€” and no, it does not care whether the inclusion will actually result in an inclusion. It fires regardless. Periodt. ๐Ÿ’…

Argument descriptions, one by one, no skipping:

  • HashLoc โ€” Argument Value Syntax: "(file):(line):(col)" ยท Clang C++ Type: SourceLocation. The location of the '#' that starts the inclusion directive. โญ
  • IncludeTok โ€” Argument Value Syntax: (token) ยท Clang C++ Type: const Token. The token that indicates the kind of inclusion directive, e.g., 'include' or 'import'.
  • FileName โ€” Argument Value Syntax: "(file)" ยท Clang C++ Type: StringRef. The name of the file being included, as written in the source code (so like, literally, don't go normalizing it).
  • IsAngled โ€” Argument Value Syntax: (true|false) ยท Clang C++ Type: bool. Whether the file name was enclosed in angle brackets; otherwise, it was enclosed in quotes. true = angled, false = quoted, iykyk ๐Ÿฆ‹
  • FilenameRange โ€” Argument Value Syntax: "(file)" ยท Clang C++ Type: CharSourceRange. The character range of the quotes or angle brackets for the written file name.
  • File โ€” Argument Value Syntax: "(file)" ยท Clang C++ Type: const FileEntry. The actual file that may be included by this inclusion directive. Note the "may" โ€” it's giving maybe, it's giving uncertain ๐Ÿ’€
  • SearchPath โ€” Argument Value Syntax: "(path)" ยท Clang C++ Type: StringRef. Contains the search path which was used to find the file in the file system.
  • RelativePath โ€” Argument Value Syntax: "(path)" ยท Clang C++ Type: StringRef. The path relative to SearchPath, at which the include file was found.
  • Imported โ€” Argument Value Syntax: ((module name)|(null)) ยท Clang C++ Type: const Module. The module, whenever an inclusion directive was automatically turned into a module import or null otherwise. So it's either the whole module or absolutely nothing, no in-between ๐Ÿงš

Example, verbatim, because we do not gatekeep:

- 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

Doc link again: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#af32dcf1b8b7c179c7fcd3e24e89830fe

moduleImport is called when there was an explicit module-import syntax. That's it, that's the whole vibe check โ€” explicit syntax only, no sneaky implicit business โœจ

Argument descriptions, bestie โ€” here's the tea on what each one actually is ๐Ÿ’…

  • ImportLoc โ€” value syntax is "(file):(line):(col)", Clang C++ type is SourceLocation. It's literally just the location of the import directive token ๐Ÿ“
  • Path โ€” value syntax is "(path)", Clang C++ type is ModuleIdPath. These are the identifiers (and their locations) of the module "path" ๐Ÿงญ
  • Imported โ€” value syntax is ((module name)|(null)), Clang C++ type is const Module. It's the imported module, and yes it can be null if importing failed โ€” no cap, that's a real possibility ๐Ÿ˜ญ

Example::

- Callback: moduleImport
  ImportLoc: "d:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-modules.cpp:4:2"
  Path: [{Name: Level1B, Loc: "d:/Clang/llvmnewmod/clang-tools-extra/test/pp-trace/pp-trace-modules.cpp:4:9"}, {Name: Level2B, Loc: "d:/Clang/llvmnewmod/clang-tools-extra/test/pp-trace/pp-trace-modules.cpp:4:17"}]
  Imported: Level2B

๐Ÿซถ EndOfMainFile Callback

Link for the curious: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a63e170d069e99bc1c9c7ea0f3bed8bcc

EndOfMainFile gets called the moment the end of the main file is reached. That's it, that's the whole vibe โœจ

Argument descriptions:

  • (no arguments) โ€” value syntax: (nada), Clang C++ type: (nada), description: (nada) ๐Ÿ“

Example::

- Callback: EndOfMainFile

๐Ÿ’– Ident Callback

Link: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a3683f1d1fa513e9b6193d446a5cc2b66

Ident fires when a #ident or #sccs directive is read ๐Ÿ“–โœจ

Argument descriptions:

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation. It's the location of the directive ๐Ÿ“
  • str โ€” value syntax (name), Clang C++ type const std::string. 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$"

๐ŸŒˆ PragmaDirective Callback

Link: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a0a2d7a72c62184b3cbde31fb62c6f2f7

PragmaDirective gets called when it starts reading any pragma directive ๐Ÿ‘‘

Argument descriptions:

  • Loc โ€” value syntax "(file):(line):(col)", Clang C++ type SourceLocation. The location of the directive ๐Ÿ“
  • Introducer โ€” value syntax (PIK_HashPragma|PIK__Pragma|PIK___pragma), Clang C++ type PragmaIntroducerKind. The type of the pragma directive ๐Ÿ”ฅ

โ€ฆaaaand that's the bottom border of the table we were in, so you're officially caught up โœจ


PragmaComment Callback ๐Ÿ’ฌ

PragmaComment fires when a #pragma comment directive gets read. The docs link for it is https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ace0d940fc2c12ab76441466aab58dc37 โ€” go peek if you want the deep lore ๐Ÿฆ‹

Argument descriptions, in all their glory:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value        โ”‚ Clang C++ Type โ”‚ Description      โ”‚
โ”‚               โ”‚ Syntax                โ”‚                โ”‚                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of  โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the directive.   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Kind          โ”‚ ((name)|(null))       โ”‚ const          โ”‚ The comment kind โ”‚
โ”‚               โ”‚                       โ”‚ IdentifierInfo โ”‚ symbol.          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Str           โ”‚ (message directive)   โ”‚ const          โ”‚ The comment      โ”‚
โ”‚               โ”‚                       โ”‚ std::string    โ”‚ message          โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ directive.       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example, bestie โ€” and yes, that kernel32.lib is giving very "compiling on Windows at 2am" energy ๐Ÿ˜ญ

- Callback: PragmaComment
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Kind: library
  Str: kernel32.lib

PragmaDetectMismatch Callback ๐Ÿซถ

PragmaDetectMismatch gets called the moment a #pragma detect_mismatch directive is read. Link for the nerds: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ab11158c9149fb8ad8af1903f4a6cd65d ๐Ÿ’…

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value        โ”‚ Clang C++ Type โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                โ”‚                โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the directive.  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Name          โ”‚ "(name)"              โ”‚ const          โ”‚ The name.       โ”‚
โ”‚               โ”‚                       โ”‚ std::string    โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Value         โ”‚ (string)              โ”‚ const          โ”‚ The value.      โ”‚
โ”‚               โ”‚                       โ”‚ std::string    โ”‚                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example โ€” it's literally just name and value, iconic minimalism tbh ๐ŸŽ€

- 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. Doxygen link: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a57cdccb6dcc07e926513ac3d5b121466 ๐ŸŒธ

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value        โ”‚ Clang C++ Type โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                โ”‚                โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the directive.  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ DebugType     โ”‚ (string)              โ”‚ StringRef      โ”‚ Indicates type  โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ of debug        โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ message.        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example:

- Callback: PragmaDebug
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  DebugType: warning

PragmaMessage Callback ๐Ÿ“ข

PragmaMessage shows up when a #pragma message directive is read. Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#abb42935d9a9fd8e2c4f51cfdc4ea2ae1 ๐Ÿ‘‘

Argument descriptions โ€” four args, all of them eating, no crumbs:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value                      โ”‚ Clang C++ Type                 โ”‚ Description      โ”‚
โ”‚               โ”‚ Syntax                              โ”‚                                โ”‚                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)"               โ”‚ SourceLocation                 โ”‚ The location of  โ”‚
โ”‚               โ”‚                                     โ”‚                                โ”‚ the directive.   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Namespace     โ”‚ (name)                              โ”‚ StringRef                      โ”‚ The namespace of โ”‚
โ”‚               โ”‚                                     โ”‚                                โ”‚ the message      โ”‚
โ”‚               โ”‚                                     โ”‚                                โ”‚ directive.       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Kind          โ”‚ (PMK_Message|PMK_Warning|PMK_Error) โ”‚ PPCallbacks::PragmaMessageKind โ”‚ The type of the  โ”‚
โ”‚               โ”‚                                     โ”‚                                โ”‚ message          โ”‚
โ”‚               โ”‚                                     โ”‚                                โ”‚ directive.       โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Str           โ”‚ (string)                            โ”‚ StringRef                      โ”‚ The text of the  โ”‚
โ”‚               โ”‚                                     โ”‚                                โ”‚ message          โ”‚
โ”‚               โ”‚                                     โ”‚                                โ”‚ directive.       โ”‚

Lowkey the Kind field is the main character here โ€” PMK_Message, PMK_Warning and PMK_Error are your three flavours, and PPCallbacks::PragmaMessageKind is the type holding them ๐ŸŒˆ

โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example::

- Callback: PragmaMessage
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Namespace: "GCC"
  Kind: PMK_Message
  Str: The message text.

โœจ PragmaDiagnosticPush Callback โœจ

PragmaDiagnosticPush <https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a0f3ff19762baa38fe6c5c58022d32979> Callback

Okay bestie, so PragmaDiagnosticPush gets called the moment a #pragma gcc diagnostic push directive gets read. That's it, that's the whole vibe ๐Ÿซถ

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value        โ”‚ Clang C++ Type โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                โ”‚                โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the directive.  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Namespace     โ”‚ (name)                โ”‚ StringRef      โ”‚ Namespace name. โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example::

- Callback: PragmaDiagnosticPush
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Namespace: "GCC"

๐ŸŒธ PragmaDiagnosticPop Callback ๐ŸŒธ

PragmaDiagnosticPop <https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ac94d789873122221fba8d76f6c5ea45e> Callback

And then PragmaDiagnosticPop is literally the other half of the duo โ€” it fires when a #pragma gcc diagnostic pop directive is read. Push, pop, iconic behavior honestly ๐Ÿ’…

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value        โ”‚ Clang C++ Type โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                โ”‚                โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the directive.  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Namespace     โ”‚ (name)                โ”‚ StringRef      โ”‚ Namespace name. โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example::

- Callback: PragmaDiagnosticPop
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Namespace: "GCC"

๐Ÿ’œ PragmaDiagnostic Callback ๐Ÿ’œ

PragmaDiagnostic <https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#afe7938f38a83cb7b4b25a13edfdd7bdd> Callback

Not me about to explain the main character of the pragma family ๐Ÿ˜ญ PragmaDiagnostic is called when a #pragma gcc diagnostic directive is read. fr fr, that's the whole thing.

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value                                 โ”‚ Clang C++ Type โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                                         โ”‚                โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)"                          โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                                                โ”‚                โ”‚ the directive.  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Namespace     โ”‚ (name)                                         โ”‚ StringRef      โ”‚ Namespace name. โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ mapping       โ”‚ (0|MAP_IGNORE|MAP_WARNING|MAP_ERROR|MAP_FATAL) โ”‚ diag::Severity โ”‚ Mapping type.   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Str           โ”‚ (string)                                       โ”‚ StringRef      โ”‚ Warning/error   โ”‚
โ”‚               โ”‚                                                โ”‚                โ”‚ name.           โ”‚

And here's what that one actually looks like when it fires ๐Ÿ‘‡

- Callback: PragmaDiagnostic
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Namespace: "GCC"
  mapping: MAP_WARNING
  Str: WarningName

๐Ÿฆ‹ PragmaOpenCLExtension Callback

Docs, if you wanna go full nerd mode: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a92a20a21fadbab4e2c788f4e27fe07e7

PragmaOpenCLExtension gets called whenever an OpenCL extension gets either disabled or enabled via a pragma. So basically: your OpenCL code said "turn this on" or "turn this off" and this is the callback that heard it. ๐Ÿ’…

Argument descriptions, bestie:

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

Example, fr fr:

- Callback: PragmaOpenCLExtension
  NameLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:10"
  Name: Name
  StateLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:18"
  State: 1

โญ PragmaWarning Callback

Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#aa17169d25fa1cf0a6992fc944d1d8730

PragmaWarning is called when a #pragma warning directive is read. That's it, that's the whole job. ๐Ÿ™ˆ

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.
  • Ids โ€” value syntax: [(number)[, ...]] โ€” Clang C++ type: ArrayRef<int> โ€” the warning numbers.

Example:

- Callback: PragmaWarning
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  WarningSpec: disable
  Ids: 1,2,3

๐ŸŒธ PragmaWarningPush Callback

Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ae5626ef70502687a859f323a809ed0b6

PragmaWarningPush fires when a #pragma warning(push) directive is read. Stack that warning state up, queen. ๐Ÿ‘‘

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.

Example:

- Callback: PragmaWarningPush
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"
  Level: 1

๐ŸŽ€ PragmaWarningPop Callback

Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#ac98d502af8811b8a6e7342d7cd2b3b95

PragmaWarningPop</purl> โ€” okay wait, PragmaWarningPop is called when a #pragma warning(pop) directive is read. The push/pop girlies stay winning. โœจ

Argument descriptions:

  • Loc โ€” value syntax: "(file):(line):(col)" โ€” Clang C++ type: SourceLocation โ€” the location of the directive.

Example:

- Callback: PragmaWarningPop
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-pragma.cpp:3:1"

๐Ÿ”ฅ MacroExpands Callback

Docs: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a9bc725209d3a071ea649144ab996d515

MacroExpands is called when ::HandleMacroExpandedIdentifier when a macro invocation is found. (Yes, that's the sentence, verbatim, we do not editorialize.) Macro moment. ๐Ÿ’…

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 โ€” argument value syntax: [(name)|(number)|<(token name)>[, ...]], and the Clang C++ type is const MacroArgs. What is it tho? It's the argument tokens, bestie. Names and numbers are literal, and everything else shows up in the form '<' tokenName '>'. ๐Ÿ’…

Example time, no cap:

- Callback: MacroExpands
  MacroNameTok: X_IMPL
  MacroDirective: MD_Define
  Range: [(nonfile), (nonfile)]
  Args: [a <plus> y, b]

๐Ÿฆ‹ MacroDefined Callback

MacroDefined is called when a macro definition is seen. Like, it literally pulls up. ๐Ÿ‘€

Link for the curious (iykyk): https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a8448fc9f96f22ad1b93ff393cffc5a76

Argument descriptions, served fresh:

  • MacroNameTok โ€” argument value syntax (token), Clang C++ type const Token. It's the macro name token. ๐ŸŽ€
  • MacroDirective โ€” argument value syntax (MD_Define|MD_Undefine|MD_Visibility), Clang C++ type const MacroDirective. This is the kind of macro directive, coming straight from the MacroDirective structure. โœจ

Example:

- Callback: MacroDefined
  MacroNameTok: X_IMPL
  MacroDirective: MD_Define

๐ŸŒˆ MacroUndefined Callback

MacroUndefined is called when a macro #undef is seen. So when something gets un-defined, this callback is on the scene. ๐Ÿซถ

Link: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#acb80fc6171a839db8e290945bf2c9d7a

Argument descriptions:

  • MacroNameTok โ€” argument value syntax (token), Clang C++ type const Token. The macro name token, iconic as always. ๐Ÿ’–
  • MacroDirective โ€” argument 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

โญ Defined Callback

Defined is called when the 'defined' operator is seen. Yes, that defined. ๐Ÿ‘‘

Link: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a3cc2a644533d0e4088a13d2baf90db94

Argument descriptions:

  • MacroNameTok โ€” argument value syntax (token), Clang C++ type const Token. The macro name token. ๐ŸŽ€
  • MacroDirective โ€” argument value syntax (MD_Define|MD_Undefine|MD_Visibility), Clang C++ type const MacroDirective. The kind of macro directive from the MacroDirective structure. โœจ
  • Range โ€” argument value syntax ["(file):(line):(col)", "(file):(line):(col)"], Clang C++ type SourceRange. It's the source range for the directive. ๐Ÿ“

ok so picture this: Defined just fired and the trace it spits out looks a little like this ๐Ÿ‘‡

- Callback: Defined
  MacroNameTok: MACRO
  MacroDirective: (null)
  Range: ["D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:5", "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:19"]

๐Ÿฆ‹ SourceRangeSkipped callback

Docs live at https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#abdb4ebe11610f079ac33515965794b46

Callback SourceRangeSkipped gets called when a source range is skipped. like, the preprocessor just hopped right over it ๐Ÿ’€

Argument descriptions:

  • Range โ€” argument value syntax ["(file):(line):(col)", "(file):(line):(col)"], Clang C++ type SourceRange, and it means the source range skipped.

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

Docs live at https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a645edcb0d6becbc6f256f02fd1287778

Callback If gets called when an #if is seen. the moment that directive shows up, bestie, this fires ๐ŸŒธ

Argument descriptions:

  • Loc โ€” argument value syntax "(file):(line):(col)", Clang C++ type SourceLocation, and it means the location of the directive.
  • ConditionRange โ€” argument value syntax ["(file):(line):(col)", "(file):(line):(col)"], Clang C++ type SourceRange, and it means the source range for the condition.
  • ConditionValue โ€” argument value syntax (true|false), Clang C++ type bool, and it means 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

Docs live at https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a180c9e106a28d60a6112e16b1bb8302a

Callback Elif gets called when an #elif is seen. it's giving "second chance" energy fr fr ๐Ÿซถ

Argument descriptions:

  • Loc โ€” argument value syntax "(file):(line):(col)", Clang C++ type SourceLocation, and it means the location of the directive.
  • ConditionRange โ€” argument value syntax ["(file):(line):(col)", "(file):(line):(col)"], Clang C++ type SourceRange, and it means the source range for the condition.
  • ConditionValue โ€” argument value syntax (true|false), Clang C++ type bool, and it means the condition value.
  • IfLoc โ€” argument value syntax "(file):(line):(col)", Clang C++ type SourceLocation, and it means the location of the directive.

Example:

- Callback: Elif
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:10:2"
  ConditionRange: ["D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:10:4", "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:11:1"]
  ConditionValue: false
  IfLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:2"

๐ŸŽ€ Ifdef callback

Docs live at https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a0ce79575dda307784fd51a6dd4eec33d

Callback Ifdef gets called when an #ifdef is seen. iconic directive, iconic callback, no notes ๐Ÿ’…

Argument descriptions:

  • Loc โ€” argument value syntax "(file):(line):(col)", Clang C++ type SourceLocation, and it means the location of the directive.
  • MacroNameTok โ€” argument value syntax (token), Clang C++ type const Token, and it means the macro name token.
  • MacroDirective โ€” argument value syntax (MD_Define|MD_Undefine|MD_Visibility), Clang C++ type const MacroDirective, and it means the kind of macro directive from the MacroDirective structure.

Example:

- Callback: Ifdef
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-conditional.cpp:3:1"
  MacroNameTok: MACRO
  MacroDirective: MD_Define

๐ŸŒˆ Ifndef callback

Docs live at https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html#a767af69f1cdcc4cd880fa2ebf77ad3ad

Callback Ifndef gets called when an #ifndef is seen. the "wait, is this NOT defined?" queen ๐Ÿ‘‘

Argument descriptions:

  • Loc โ€” argument value syntax "(file):(line):(col)", Clang C++ type SourceLocation, and it means the location of the directive.
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ MacroNameTok   โ”‚ (token)                               โ”‚ const Token    โ”‚ The macro name  โ”‚
โ”‚                โ”‚                                       โ”‚                โ”‚ token.          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ MacroDirective โ”‚ (MD_Define|MD_Undefine|MD_Visibility) โ”‚ const          โ”‚ The kind of     โ”‚
โ”‚                โ”‚                                       โ”‚ MacroDirective โ”‚ macro directive โ”‚
โ”‚                โ”‚                                       โ”‚                โ”‚ from the        โ”‚
โ”‚                โ”‚                                       โ”‚                โ”‚ MacroDirective  โ”‚
โ”‚                โ”‚                                       โ”‚                โ”‚ structure.      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

okay so this table just kept going and we are SO close to the end of it bestie ๐Ÿ’– โ€” MacroNameTok is literally the macro name token, typed const Token. And MacroDirective is (MD_Define|MD_Undefine|MD_Visibility), typed const MacroDirective, and it tells you the kind of macro directive, pulled straight out of the MacroDirective structure. no notes, it's giving very "here's the receipts" energy ๐Ÿงพโœจ

Example::

- Callback: Ifndef
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-conditional.cpp:3:1"
  MacroNameTok: MACRO
  MacroDirective: MD_Define

๐ŸŒธ Else โ€” Callback ๐ŸŒธ

Else Callback is the one that shows up when an #else is seen. iconic behaviour honestly ๐Ÿ’…

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value        โ”‚ Clang C++ Type โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                โ”‚                โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the else        โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ directive.      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ IfLoc         โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the if          โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ directive.      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

So Loc is a SourceLocation written as "(file):(line):(col)" and it's where the else directive lives. IfLoc is also a SourceLocation in that same "(file):(line):(col)" shape, and it's where the if directive lives. two locations, one callback, we love a pair ๐Ÿซถ

Example::

- Callback: Else
  Loc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:10:2"
  IfLoc: "D:/Clang/llvm/clang-tools-extra/test/pp-trace/pp-trace-macro.cpp:8:2"

๐ŸŒˆ Endif โ€” Callback ๐ŸŒˆ

Endif Callback is called when an #endif is seen. closing time, you don't have to go home but you can't stay here ๐ŸŽ€

Argument descriptions:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Argument Name โ”‚ Argument Value        โ”‚ Clang C++ Type โ”‚ Description     โ”‚
โ”‚               โ”‚ Syntax                โ”‚                โ”‚                 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Loc           โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the endif       โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ directive.      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ IfLoc         โ”‚ "(file):(line):(col)" โ”‚ SourceLocation โ”‚ The location of โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ the if          โ”‚
โ”‚               โ”‚                       โ”‚                โ”‚ directive.      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Same deal as before, fr fr: Loc is the SourceLocation of the endif directive in "(file):(line):(col)" form, and IfLoc is the SourceLocation of the if directive, also "(file):(line):(col)". lowkey symmetrical, highkey satisfying โœจ

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 ๐Ÿ› ๏ธ

To build from source, bestie, here's the whole itinerary:

  1. Read Getting Started with the LLVM System <https://llvm.org/docs/GettingStarted.html> and Clang Tools Documentation <https://clang.llvm.org/docs/ClangTools.html> for information on getting sources for LLVM, Clang, and Clang Extra Tools.
  2. Getting Started with the LLVM System <https://llvm.org/docs/GettingStarted.html> and Building LLVM with CMake <https://llvm.org/docs/CMake.html> give directions for how to build. With sources all checked out into the right place the LLVM build will build Clang Extra Tools and their dependencies automatically.
    • If using CMake, you can also use the pp-trace target to build just the pp-trace tool and its dependencies.

๐Ÿ’œ 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. it literally reads your code and your comments and turns them into docs, main character behaviour ๐Ÿ’…

heads up bestie: the tool is in a very early development stage, so you might encounter bugs and crashes. Submitting reports with information about how to reproduce the issue to the LLVM bug tracker <https://github.com/llvm/llvm-project/issues/> will definitely help the project. If you have any ideas or suggestions, please put a feature request there too ๐Ÿซถ

๐Ÿฆ‹ Use ๐Ÿฆ‹

clang-doc is a LibTooling-based tool (<https://clang.llvm.org/docs/LibTooling.html>), which means it requires a compile command database for your project. For an example of how to do that, see How To Setup Tooling For LLVM <https://clang.llvm.org/docs/HowToSetupToolingForLLVM.html>. no database, no vibes, that's just how it is โœจ

By default the tool will process a list of files:

$ clang-doc File1.cpp File2.cpp ... FileN.cpp

It can also be used with a compile commands database:

$ clang-doc --executor=all-TUs compile_commands.json

And if you only want a subset of files from the database, hit it with the --filter flag:

$ clang-doc --executor=all-TUs --filter=File[0-9]+.cpp compile_commands.json

๐ŸŒธ Output ๐ŸŒธ

clang-doc produces a directory of documentation. One file is produced for each namespace and record in the project source code, and each of those files contains all documentation for that item โ€” including contained functions, methods, and enums. it's giving "everything in its right place" ๐ŸŽ€

The top-level directory is configurable through the output flag:

$ clang-doc --output=output/directory/ compile_commands.json

๐ŸŽ€ Configuration ๐ŸŽ€

Configuration for clang-doc is currently limited to command-line options. In the future, it may develop the ability to use a configuration file, but no such efforts are currently in progress. so rn it's flags only, bestie, don't go looking for a config file that doesn't exist ๐Ÿ™ˆ

๐Ÿ‘‘ 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.

And the important lil footnote, bestie โ€” these three flags should only be used if format is set to html: -repository, -source-root, -stylesheets. periodt ๐Ÿ’…

๐Ÿ’– CLANG TOOLS EXTRA MAINTAINERS ๐Ÿ’–

Okay so this file? It's literally the who's-who list of the maintainers for the Extra Clang Tools project, linked up at https://llvm.org/docs/DeveloperPolicy.html#maintainers and https://clang.llvm.org/extra/index.html. These are your people. Your code-review besties. โœจ

  • Active Maintainers
    • Lead Maintainer
    • clang-tidy
    • clang-query
    • clang-doc
    • clangd
  • Inactive Maintainers
    • Emeritus Lead Maintainers
    • Inactive component maintainers

๐Ÿซถ Active Maintainers

These are the currently-active maintainers for the project, bestie. Reach out to them for code reviews, questions about their area of expertise, or literally any other assistance. They're the moment. ๐Ÿ‘‘

๐ŸŒŸ Lead Maintainer

Aaron Ballman โ€” aaron@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord)

๐Ÿงน clang-tidy

Congcong Cai โ€” congcongcai0907@163.com (email), HerrCai0907 (GitHub), HerrCai0907 (Discourse)

Julian Schmidt โ€” git.julian.schmidt@gmail.com (email), 5chmidti (GitHub), 5chmidti (Discourse), 5chmidti (Discord)

Piotr Zegar โ€” me@piotrzegar.pl (email), PiotrZSL (GitHub), PiotrZSL (Discourse), PiotrZSL (Discord)

Victor Baranov โ€” bar.victor.2002@gmail.com (email), vbvictor (GitHub), vbvictor (Discourse), vbvictor (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)

Peter Chou โ€” peterchou411@gmail.com (email), PeterChou1 (GitHub), PeterChou1 (Discourse), .peterchou (Discord)

๐Ÿฆ‹ clangd

Nathan Ridge โ€” zeratul976@hotmail.com (email), HighCommander4 (GitHub), HighCommander4 (Discourse), nridge (Discord)

Chris Bieneman โ€” chris.bieneman@gmail.com (email), llvm-beanz (GitHub), beanz (Discord), beanz (Discourse)

Kadir ร‡etinkaya โ€” kadircet@google.com (email), kadircet (GitHub) kadircet (Discourse), kadircet (Discord)


๐ŸŒธ Inactive Maintainers

The following people have graciously spent time performing maintainership responsibilities but are no longer active in that role. Thank you for all your help with the success of the project! ๐Ÿ˜ญ๐Ÿ’– Absolute legends, no cap.

๐Ÿ•Š๏ธ 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


The Doxygen documentation describes the internal software that makes up the tools of clang-tools-extra โ€” not the external use of these tools. The Doxygen documentation contains no instructions about how to use the tools, only the APIs that make up the software. For usage instructions, please see the user's guide or reference manual for each tool. ๐Ÿ“–

  • Doxygen documentation <doxygen/annotated.html>
Note: โš ๏ธ This documentation is generated directly from the source code with doxygen. Since the tools of clang-tools-extra are constantly under active development, what you're about to read is out of date! (iykyk ๐Ÿ˜ญ)
  • Index <>
  • Search Page <>

โœ๏ธ Author

The Clang Team

2007-2026, The Clang Team


And that's a wrap on the whole page, bestie!! ๐ŸŽ€ This edition is from Extra Clang Tools 22, dated Jul 16, 2026, and it's the EXTRACLANGTOOLS(1) manual page. Slay, go forth and lint. ๐Ÿ’…โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from