Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
.. title:: clang-tidy - bugprone-tagged-union-member-count

bugprone-tagged-union-member-count
==================================

Gives warnings for tagged unions, where the number of tags is
different from the number of data members inside the union.

A struct or a class is considered to be a tagged union if it has
exactly one union data member and exactly one enum data member and
any number of other data members that are neither unions or enums.

Example:

.. code-block:: c++

enum Tags {
Tag1,
Tag2,
};

struct TaggedUnion { // warning: tagged union has more data members (3) than tags (2)
enum Tags Kind;
union {
int I;
float F;
char *Str;
} Data;
};
How enum constants are counted
------------------------------

The main complicating factor when counting the number of enum constants is that
some of them might be auxiliary values that purposefully don't have a corresponding union
data member and are used for something else. For example the last enum constant
sometimes explicitly "points to" the last declared valid enum constant or
tracks how many enum constants have been declared.

For an illustration:

.. code-block:: c++

enum TagWithLast {
Tag1 = 0,
Tag2 = 1,
Tag3 = 2,
LastTag = 2
};

enum TagWithCounter {
Tag1, // is 0
Tag2, // is 1
Tag3, // is 2
TagCount, // is 3
};

The check counts the number of distinct values among the enum constants and not the enum
constants themselves. This way the enum constants that are essentially just aliases of other
enum constants are not included in the final count.

Handling of counting enum constants (ones like :code:`TagCount` in the previous code example)
is done by decreasing the number of enum values by one if the name of the last enum constant
starts with a prefix or ends with a suffix specified in :option:`CountingEnumPrefixes`,
:option:`CountingEnumSuffixes` and it's value is one less than the total number of distinct
values in the enum.

When the final count is adjusted based on this heuristic then a diagnostic note is emitted
that shows which enum constant matched the criteria.

The heuristic can be disabled entirely (:option:`EnableCountingEnumHeuristic`) or
configured to follow your naming convention (:option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes`).
The strings specified in :option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes` are matched
case insensitively.

Example counts:

.. code-block:: c++

// 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
-------

.. option:: EnableCountingEnumHeuristic

This option enables or disables the counting enum heuristic.
It uses the prefixes and suffixes specified in the options
:option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes` to find counting enum constants by
using them for prefix and suffix matching.

This option is enabled by default.

When :option:`EnableCountingEnumHeuristic` is `false`:

.. code-block:: c++

enum TagWithCounter {
Tag1,
Tag2,
Tag3,
TagCount,
};

struct TaggedUnion {
TagWithCounter Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
When :option:`EnableCountingEnumHeuristic` is `true`:

.. code-block:: c++

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;
};
.. option:: CountingEnumPrefixes

See :option:`CountingEnumSuffixes` below.

.. option:: CountingEnumSuffixes

CountingEnumPrefixes and CountingEnumSuffixes are lists of semicolon
separated strings that are used to search for possible counting enum constants.
These strings are matched case insensitively as prefixes and suffixes
respectively on the names of the enum constants.
If :option:`EnableCountingEnumHeuristic` is `false` then these options do nothing.

The default value of :option:`CountingEnumSuffixes` is `count` and of
:option:`CountingEnumPrefixes` is the empty string.

When :option:`EnableCountingEnumHeuristic` is `true` and :option:`CountingEnumSuffixes`
is `count;size`:

.. code-block:: c++

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 :option:`EnableCountingEnumHeuristic` is `true` and :option:`CountingEnumPrefixes` is `maxsize;last_`

.. code-block:: c++

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;
};
.. option:: StrictMode

When enabled, the check will also give a warning, when the number of tags
is greater than the number of union data members.

This option is disabled by default.

When :option:`StrictMode` is `false`:

.. code-block:: c++

struct TaggedUnion {
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};

When :option:`StrictMode` is `true`:

.. code-block:: c++

struct TaggedUnion { // warning: tagged union has fewer data members (2) than tags (3)
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};
2 changes: 2 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Clang-Tidy Checks
:doc:`bugprone-assert-side-effect <bugprone/assert-side-effect>`,
:doc:`bugprone-assignment-in-if-condition <bugprone/assignment-in-if-condition>`,
:doc:`bugprone-bad-signal-to-kill-thread <bugprone/bad-signal-to-kill-thread>`,
:doc:`bugprone-bitwise-pointer-cast <bugprone/bitwise-pointer-cast>`,
:doc:`bugprone-bool-pointer-implicit-conversion <bugprone/bool-pointer-implicit-conversion>`, "Yes"
:doc:`bugprone-branch-clone <bugprone/branch-clone>`,
:doc:`bugprone-casting-through-void <bugprone/casting-through-void>`,
Expand Down Expand Up @@ -145,6 +146,7 @@ Clang-Tidy Checks
:doc:`bugprone-suspicious-stringview-data-usage <bugprone/suspicious-stringview-data-usage>`,
:doc:`bugprone-swapped-arguments <bugprone/swapped-arguments>`, "Yes"
:doc:`bugprone-switch-missing-default-case <bugprone/switch-missing-default-case>`,
:doc:`bugprone-tagged-union-member-count <bugprone/tagged-union-member-count>`,
:doc:`bugprone-terminating-continue <bugprone/terminating-continue>`, "Yes"
:doc:`bugprone-throw-keyword-missing <bugprone/throw-keyword-missing>`,
:doc:`bugprone-too-small-loop-variable <bugprone/too-small-loop-variable>`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ into:

The check uses the same format-string-conversion algorithm as
`modernize-use-std-print <../modernize/use-std-print.html>`_ and its
shortcomings are described in the documentation for that check.
shortcomings and behaviour in combination with macros are described in the
documentation for that check.

Options
-------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ into:
std::println(stderr, "The {} is {:3}", description, value);

If the `ReplacementPrintFunction` or `ReplacementPrintlnFunction` options
are left, or assigned to their default values then this check is only
enabled with `-std=c++23` or later.
are left at or set to their default values then this check is only enabled
with `-std=c++23` or later.

Macros starting with ``PRI`` and ``__PRI`` from `<inttypes.h>` are
expanded, escaping is handled and adjacent strings are concatenated to form
a single ``StringLiteral`` before the format string is converted. Use of
any other macros in the format string will cause a warning message to be
emitted and no conversion will be performed. The converted format string
will always be a single string literal.

The check doesn't do a bad job, but it's not perfect. In particular:

Expand All @@ -34,13 +41,10 @@ The check doesn't do a bad job, but it's not perfect. In particular:
possible.

- At the point that the check runs, the AST contains a single
``StringLiteral`` for the format string and any macro expansion, token
pasting, adjacent string literal concatenation and escaping has been
handled. Although it's possible for the check to automatically put the
escapes back, they may not be exactly as they were written (e.g.
``"\x0a"`` will become ``"\n"`` and ``"ab" "cd"`` will become
``"abcd"``.) This is helpful since it means that the ``PRIx`` macros from
``<inttypes.h>`` are removed correctly.
``StringLiteral`` for the format string where escapes have been expanded.
The check tries to reconstruct escape sequences, they may not be the same
as they were written (e.g. ``"\x41\x0a"`` will become ``"A\n"`` and
``"ab" "cd"`` will become ``"abcd"``.)

- It supports field widths, precision, positional arguments, leading zeros,
leading ``+``, alignment and alternative forms.
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/docs/clang-tidy/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ An overview of all the command-line options:
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-regex'.
HeaderFilterRegex - Same as '--header-filter'.
ImplementationFileExtensions - File extensions to consider to determine if a
given diagnostic is located in an
implementation file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <string>
#include <utility>

namespace clang {
class SourceLocation;
Expand Down Expand Up @@ -62,7 +63,8 @@ void walkUsed(llvm::ArrayRef<Decl *> ASTRoots,

struct AnalysisResults {
std::vector<const Include *> Unused;
std::vector<std::string> Missing; // Spellings, like "<vector>"
// Spellings, like "<vector>" paired with the Header that generated it.
std::vector<std::pair<std::string, Header>> Missing;
};

/// Determine which headers should be inserted or removed from the main file.
Expand Down
16 changes: 8 additions & 8 deletions clang-tools-extra/include-cleaner/lib/Analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
Expand Down Expand Up @@ -84,7 +84,7 @@ analyze(llvm::ArrayRef<Decl *> ASTRoots,
auto &SM = PP.getSourceManager();
const auto MainFile = *SM.getFileEntryRefForID(SM.getMainFileID());
llvm::DenseSet<const Include *> Used;
llvm::StringSet<> Missing;
llvm::StringMap<Header> Missing;
if (!HeaderFilter)
HeaderFilter = [](llvm::StringRef) { return false; };
OptionalDirectoryEntryRef ResourceDir =
Expand Down Expand Up @@ -119,7 +119,7 @@ analyze(llvm::ArrayRef<Decl *> ASTRoots,
Satisfied = true;
}
if (!Satisfied)
Missing.insert(std::move(Spelling));
Missing.try_emplace(std::move(Spelling), Providers.front());
});

AnalysisResults Results;
Expand All @@ -144,8 +144,8 @@ analyze(llvm::ArrayRef<Decl *> ASTRoots,
}
Results.Unused.push_back(&I);
}
for (llvm::StringRef S : Missing.keys())
Results.Missing.push_back(S.str());
for (auto &E : Missing)
Results.Missing.emplace_back(E.first().str(), E.second);
llvm::sort(Results.Missing);
return Results;
}
Expand All @@ -158,9 +158,9 @@ std::string fixIncludes(const AnalysisResults &Results,
// Encode insertions/deletions in the magic way clang-format understands.
for (const Include *I : Results.Unused)
cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 1, I->quote())));
for (llvm::StringRef Spelled : Results.Missing)
cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 0,
("#include " + Spelled).str())));
for (auto &[Spelled, _] : Results.Missing)
cantFail(R.add(
tooling::Replacement(FileName, UINT_MAX, 0, "#include " + Spelled)));
// "cleanup" actually turns the UINT_MAX replacements into concrete edits.
auto Positioned = cantFail(format::cleanupAroundReplacements(Code, R, Style));
return cantFail(tooling::applyAllReplacements(Code, Positioned));
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/include-cleaner/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
set(LLVM_LINK_COMPONENTS Support)

add_clang_library(clangIncludeCleaner
add_clang_library(clangIncludeCleaner STATIC
Analysis.cpp
IncludeSpeller.cpp
FindHeaders.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class Action : public clang::ASTFrontendAction {
case PrintStyle::Changes:
for (const Include *I : Results.Unused)
llvm::outs() << "- " << I->quote() << " @Line:" << I->Line << "\n";
for (const auto &I : Results.Missing)
for (const auto &[I, _] : Results.Missing)
llvm::outs() << "+ " << I << "\n";
break;
case PrintStyle::Final:
Expand Down
20 changes: 12 additions & 8 deletions clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/VirtualFileSystem.h"
Expand All @@ -39,6 +40,7 @@

namespace clang::include_cleaner {
namespace {
using testing::_;
using testing::AllOf;
using testing::Contains;
using testing::ElementsAre;
Expand Down Expand Up @@ -262,10 +264,12 @@ int x = a + c;
auto Results =
analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
PP.MacroReferences, PP.Includes, &PI, AST.preprocessor());
auto CHeader = llvm::cantFail(
AST.context().getSourceManager().getFileManager().getFileRef("c.h"));

const Include *B = PP.Includes.atLine(3);
ASSERT_EQ(B->Spelled, "b.h");
EXPECT_THAT(Results.Missing, ElementsAre("\"c.h\""));
EXPECT_THAT(Results.Missing, ElementsAre(Pair("\"c.h\"", Header(CHeader))));
EXPECT_THAT(Results.Unused, ElementsAre(B));
}

Expand Down Expand Up @@ -370,7 +374,7 @@ TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());
// Check that we're spelling header using the symlink, and not underlying
// path.
EXPECT_THAT(Results.Missing, testing::ElementsAre("\"inner.h\""));
EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
// header.h should be unused.
EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));

Expand All @@ -379,7 +383,7 @@ TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
auto HeaderFilter = [](llvm::StringRef Path) { return Path == "inner.h"; };
Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor(),
HeaderFilter);
EXPECT_THAT(Results.Missing, testing::ElementsAre("\"inner.h\""));
EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
// header.h should be unused.
EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
}
Expand All @@ -389,7 +393,7 @@ TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
HeaderFilter);
// header.h should be ignored now.
EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
EXPECT_THAT(Results.Missing, testing::ElementsAre("\"inner.h\""));
EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
}
}

Expand All @@ -414,9 +418,9 @@ TEST(FixIncludes, Basic) {
Inc.add(I);

AnalysisResults Results;
Results.Missing.push_back("\"aa.h\"");
Results.Missing.push_back("\"ab.h\"");
Results.Missing.push_back("<e.h>");
Results.Missing.emplace_back("\"aa.h\"", Header(""));
Results.Missing.emplace_back("\"ab.h\"", Header(""));
Results.Missing.emplace_back("<e.h>", Header(""));
Results.Unused.push_back(Inc.atLine(3));
Results.Unused.push_back(Inc.atLine(4));

Expand All @@ -429,7 +433,7 @@ R"cpp(#include "d.h"
)cpp");

Results = {};
Results.Missing.push_back("\"d.h\"");
Results.Missing.emplace_back("\"d.h\"", Header(""));
Code = R"cpp(#include "a.h")cpp";
EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
R"cpp(#include "d.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,46 @@ typedef __UINT32_TYPE__ uint32_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __UINT8_TYPE__ uint8_t;

#define PRIdMAX "lld"
#define PRId64 "lld"
#if __WORDSIZE == 64
# define __PRI64_PREFIX "l"
#else
# define __PRI64_PREFIX "ll"
#endif

#define PRIdMAX __PRI64_PREFIX "d"
#define PRId64 __PRI64_PREFIX "d"
#define PRId32 "d"
#define PRId16 "hd"
#define PRId8 "hhd"

#define PRIiMAX "lli"
#define PRIi64 "lli"
#define PRIiMAX __PRI64_PREFIX "i"
#define PRIi64 __PRI64_PREFIX "i"
#define PRIi32 "i"
#define PRIi16 "hi"
#define PRIi8 "hhi"

#define PRIiFAST64 "lli"
#define PRIiFAST64 __PRI64_PREFIX "i"
#define PRIiFAST32 "i"
#define PRIiFAST16 "hi"
#define PRIiFAST8 "hhi"

#define PRIiLEAST64 "lli"
#define PRIiLEAST64 __PRI64_PREFIX "i"
#define PRIiLEAST32 "i"
#define PRIiLEAST16 "hi"
#define PRIiLEAST8 "hhi"

#define PRIuMAX "llu"
#define PRIu64 "llu"
#define PRIuMAX __PRI64_PREFIX "u"
#define PRIu64 __PRI64_PREFIX "u"
#define PRIu32 "u"
#define PRIu16 "hu"
#define PRIu8 "hhu"

#define PRIuFAST64 "llu"
#define PRIuFAST64 __PRI64_PREFIX "u"
#define PRIuFAST32 "u"
#define PRIuFAST16 "hu"
#define PRIuFAST8 "hhu"

#define PRIuLEAST64 "llu"
#define PRIuLEAST64 __PRI64_PREFIX "u"
#define PRIuLEAST32 "u"
#define PRIuLEAST16 "hu"
#define PRIuLEAST8 "hhu"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// RUN: %check_clang_tidy -std=c++20 %s bugprone-bitwise-pointer-cast %t

void memcpy(void* to, void* dst, unsigned long long size)
{
// Dummy implementation for the purpose of the test
}

namespace std
{
template <typename To, typename From>
To bit_cast(From from)
{
// Dummy implementation for the purpose of the test
To to{};
return to;
}

using ::memcpy;
}

void pointer2pointer()
{
int x{};
float bad = *std::bit_cast<float*>(&x); // UB, but looks safe due to std::bit_cast
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast]
float good = std::bit_cast<float>(x); // Well-defined

using IntPtr = int*;
using FloatPtr = float*;
IntPtr x2{};
float bad2 = *std::bit_cast<FloatPtr>(x2);
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast]
}

void pointer2pointer_memcpy()
{
int x{};
int* px{};
float y{};
float* py{};

memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
std::memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]

std::memcpy(&y, &x, sizeof(x));
}

// Pointer-integer conversions are allowed by this check
void int2pointer()
{
unsigned long long addr{};
float* p = std::bit_cast<float*>(addr);
std::memcpy(&p, &addr, sizeof(addr));
}

void pointer2int()
{
float* p{};
auto addr = std::bit_cast<unsigned long long>(p);
std::memcpy(&addr, &p, sizeof(p));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// RUN: %check_clang_tidy %s bugprone-bitwise-pointer-cast %t

void memcpy(void* to, void* dst, unsigned long long size)
{
// Dummy implementation for the purpose of the test
}

namespace std
{
using ::memcpy;
}

void pointer2pointer()
{
int x{};
int* px{};
float y{};
float* py{};

memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
std::memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]

std::memcpy(&y, &x, sizeof(x));
}

// Pointer-integer conversions are allowed by this check
void int2pointer()
{
unsigned long long addr{};
float* p{};
std::memcpy(&p, &addr, sizeof(addr));
}

void pointer2int()
{
unsigned long long addr{};
float* p{};
std::memcpy(&addr, &p, sizeof(p));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: false, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}'

// Warn when the heuristic is disabled and a suffix or a prefix is set explicitly.

// CHECK-MESSAGES: warning: bugprone-tagged-union-member-count: Counting enum heuristic is disabled but CountingEnumPrefixes is set
// CHECK-MESSAGES: warning: bugprone-tagged-union-member-count: Counting enum heuristic is disabled but CountingEnumSuffixes is set
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: true, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: false, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (3) than tags (4)
struct IncorrectBecauseHeuristicIsDisabledPrefixCase {
enum {
tags11,
tags22,
tags33,
lasttag,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsDisabledPrefixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
lasttags,
} Tags;
union {
char A;
short B;
int C;
long D;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (3) than tags (4)
struct IncorrectBecauseHeuristicIsDisabledSuffixCase {
enum {
tags11,
tags22,
tags33,
tags_count,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsDisabledSuffixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
tags_count,
} Tags;
union {
char A;
short B;
int C;
long D;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct IncorrectBecauseHeuristicIsEnabledPrefixCase {
enum {
tags1,
tags2,
lasttag,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsEnabledPrefixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
lasttag,
} Tags;
union {
int A;
int B;
int C;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct IncorrectBecauseHeuristicIsEnabledSuffixCase {
enum {
tags1,
tags2,
tags_count,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsEnabledSuffixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
tags_count,
} Tags;
union {
int A;
int B;
int C;
} Data;
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct CountingEnumCaseInsensitivityTest1 {
enum {
node_type_loop,
node_type_branch,
node_type_function,
node_type_count,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct CountingEnumCaseInsensitivityTest2 {
enum {
NODE_TYPE_LOOP,
NODE_TYPE_BRANCH,
NODE_TYPE_FUNCTION,
NODE_TYPE_COUNT,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TagWhereCountingEnumIsAliased {
enum {
tag_alias_counter1 = 1,
tag_alias_counter2 = 2,
tag_alias_counter3 = 3,
tag_alias_other_count = 3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (2)
struct TagWithCountingEnumButOtherValueIsAliased {
enum {
tag_alias_other1 = 1,
tag_alias_other2 = 1,
tag_alias_other3 = 3,
tag_alias_other_count = 2,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TagWhereCounterIsTheSmallest {
enum {
tag_large1 = 1000,
tag_large2 = 1001,
tag_large3 = 1002,
tag_large_count = 3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// No warnings expected, only the last enum constant can be a counting enum constant
struct TagWhereCounterLikeNameIsNotLast {
enum {
kind_count,
kind2,
last_kind1,
kind3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}' --

union Union3 {
short *Shorts;
int *Ints;
float *Floats;
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// The heuristic only considers the last enum constant
// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionPrefixAndSuffixMatch {
enum {
tags1,
tags2,
tagscount,
lasttags
} Kind;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct TaggedUnionOnlyPrefixMatch {
enum {
prefixtag1,
prefixtag2,
lastprefixtag
} Kind;
Union3 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct TaggedUnionOnlySuffixMatch {
enum {
suffixtag1,
suffixtag2,
suffixtagcount
} Kind;
Union3 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "maxsize;last", \
// RUN: }}' --

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithMaxsizeAsCounterPrefix {
enum {
twc1,
twc2,
twc3,
maxsizetwc,
} Kind;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithLastAsCounterPrefix {
enum {
twc11,
twc22,
twc33,
lasttwc,
} Kind;
Union4 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count;size", \
// RUN: }}' --

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} union4;

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithCounterCountSuffix {
enum {
twc1,
twc2,
twc3,
twc_count,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithCounterSizeSuffix {
enum {
twc11,
twc22,
twc33,
twc_size,
} Kind;
union Union4 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (2) than tags (1)
struct Incorrect {
enum {
tags1,
} Tags;
union {
char A;
short B;
} Data;
};

struct CorrectBecauseStrictModeIsDisabled { // No warnings expected
enum {
tags1,
tags2,
tags3,
} Tags;
union {
char A;
short B;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: true, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (2) than tags (3)
struct IncorrectBecauseStrictmodeIsEnabled {
enum {
tags1,
tags2,
tags3,
} Tags;
union {
char A;
short B;
} Data;
};

struct Correct { // No warnings expected
enum {
tags1,
tags2,
tags3,
} Tags;
union {
char A;
short B;
int C;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t

typedef enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
} Tags3;

typedef enum Tags4 {
tags4_1,
tags4_2,
tags4_3,
tags4_4,
} Tags4;

typedef union Union3 {
short *Shorts;
int *Ints;
float *Floats;
} Union3;

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} Union4;

// It is not obvious which enum is the tag for the union.
struct maybeTaggedUnion1 { // No warnings expected.
enum Tags3 TagA;
enum Tags4 TagB;
union Union4 Data;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion2 { // No warnings expected.
enum Tags3 Tag;
union Union3 DataB;
union Union3 DataA;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion3 { // No warnings expected.
enum Tags3 Tag;
union {
int I1;
int I2;
int I3;
};
union {
float F1;
float F2;
float F3;
};
};

// No warnings expected, because LastATag is just an alias
struct TaggedUnionWithAliasedEnumConstant {
enum {
ATag1,
ATag2,
ATag3,
LastATag = ATag3,
} Tag;
union {
float F;
int *Ints;
char Key[8];
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndPredefinedUnion {
enum Tags3 Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndInlineUnion {
enum Tags3 Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndPredefinedUnion {
enum {
TaggedUnion7tag1,
TaggedUnion7tag2,
TaggedUnion7tag3,
} Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndInlineUnion {
enum {
TaggedUnion8tag1,
TaggedUnion8tag2,
TaggedUnion8tag3,
} Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructNesting {
enum Tags3 Tag;
union {
float F;
int I;
long L;
// CHECK-MESSAGES: :[[@LINE+1]]:12: warning: tagged union has more data members (4) than tags (3)
struct innerdecl {
enum Tags3 Tag;
union Union4 Data;
} Inner;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithTypedefedTagAndTypedefedUnion {
Tags3 Tag;
Union4 Data;
};

#define DECLARE_TAGGED_UNION_STRUCT(Tag, Union, Name)\
struct Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:44: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_STRUCT(Tags3, Union4, TaggedUnionStructFromMacro);
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t
// Test check with C++ features

typedef enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
} Tags3;

typedef enum Tags4 {
tags4_1,
tags4_2,
tags4_3,
tags4_4,
} Tags4;

enum class Classtags3 {
classtags3_1,
classtags3_2,
classtags3_3,
};

enum class Typedtags3 : unsigned int {
typedtags3_1,
typedtags3_2,
typedtags3_3,
};

typedef union Union3 {
short *Shorts;
int *Ints;
float *Floats;
} Union3;

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} Union4;

// It is not obvious which enum is the tag for the union.
class MaybeTaggedUnion1 { // No warnings expected.
enum Tags3 TagA;
enum Tags4 TagB;
union Union4 Data;
};

// It is not obvious which union does the tag belong to.
class MaybeTaggedUnion2 { // No warnings expected.
enum Tags3 Tag;
union Union3 DataB;
union Union3 DataA;
};

// It is not obvious which union does the tag belong to.
class MaybeTaggedUnion3 { // No warnings expected.
enum Tags3 Tag;
union {
int I1;
int I2;
int I3;
};
union {
float F1;
float F2;
float F3;
};
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassPredefinedTagAndPredefinedUnion {
enum Tags3 Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassPredefinedTagAndInlineUnion {
enum Tags3 Tag;
union {
int *Ints;
char Characters[13];
class {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInlineTagAndPredefinedUnion {
enum {
tag1,
tag2,
tag3,
} Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInlineTagAndInlineUnion {
enum {
tag1,
tag2,
tag3,
} Tag;
union {
int *Ints;
char Characters[13];
class {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithNestedTaggedUnionClass {
enum Tags3 Tag;
union {
float F;
int I;
long L;
// CHECK-MESSAGES: :[[@LINE+1]]:11: warning: tagged union has more data members (4) than tags (3)
class Innerdecl {
enum Tags3 Tag;
union Union4 Data;
} Inner;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithTypedefedTag {
Tags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithEnumClass {
enum Classtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClasswithEnumClass {
enum Classtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithTypedEnum {
Typedtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithTypedEnum {
Typedtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct AnonymousTaggedUnionStruct {
Tags3 Tag;
union {
char A;
short B;
int C;
long D;
};
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithAnonymousUnion {
Tags3 Tag;
union {
char A;
short B;
int C;
long D;
};
};

namespace testnamespace {

enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructInNamespace {
Tags3 Tags;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInNamespace {
Tags3 Tags;
Union4 Data;
};

} // namespace testnamespace

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithNamespacedTagAndUnion {
testnamespace::Tags3 Tags;
testnamespace::Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithNamespacedTagAndUnion {
testnamespace::Tags3 Tags;
testnamespace::Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
struct TemplatedStructWithNamespacedTagAndUnion {
Tag Kind;
Union Data;
};

TemplatedStructWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedStruct3;

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
class TemplatedClassWithNamespacedTagAndUnion {
Tag Kind;
Union Data;
};

TemplatedClassWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedClass3;

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
struct TemplatedStruct {
Tag Kind;
Union Data;
};

TemplatedStruct<Tags3, Union3> TemplatedStruct1; // No warning expected
TemplatedStruct<Tags3, Union4> TemplatedStruct2;

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
class TemplatedClass {
Tag Kind;
Union Data;
};

TemplatedClass<Tags3, Union3> TemplatedClass1; // No warning expected
TemplatedClass<Tags3, Union4> TemplatedClass2;

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename T>
struct TemplatedStructButTaggedUnionPartIsNotTemplated {
Tags3 Kind;
Union4 Data;
T SomethingElse;
};

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename T>
class TemplatedClassButTaggedUnionPartIsNotTemplated {
Tags3 Kind;
Union4 Data;
T SomethingElse;
};

#define DECLARE_TAGGED_UNION_STRUCT(Tag, Union, Name)\
struct Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:44: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_STRUCT(Tags3, Union4, TaggedUnionStructFromMacro);

#define DECLARE_TAGGED_UNION_CLASS(Tag, Union, Name)\
class Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:43: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_CLASS(Tags3, Union4, TaggedUnionClassFromMacro);

// Lambdas implicitly compile down to an unnamed CXXRecordDecl and if they have captures,
// then those become unnamed fields.
void DoNotMatchLambdas() {
enum {
A
} e;
union {
long A;
char B;
} u;
auto L = [e, u] () {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t

typedef enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
} Tags3;

typedef enum Tags4 {
tags4_1,
tags4_2,
tags4_3,
tags4_4,
} Tags4;

typedef union Union3 {
short *Shorts;
int *Ints;
float *Floats;
} Union3;

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} Union4;

// It is not obvious which enum is the tag for the union.
struct maybeTaggedUnion1 { // No warnings expected.
enum Tags3 TagA;
enum Tags4 TagB;
union Union4 Data;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion2 { // No warnings expected.
enum Tags3 Tag;
union Union3 DataB;
union Union3 DataA;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion3 { // No warnings expected.
enum Tags3 Tag;
union {
int I1;
int I2;
int I3;
};
union {
float F1;
float F2;
float F3;
};
};

// No warnings expected, because LastATag is just an alias
struct TaggedUnionWithAliasedEnumConstant {
enum {
ATag1,
ATag2,
ATag3,
LastATag = ATag3,
} Tag;
union {
float F;
int *Ints;
char Key[8];
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndPredefinedUnion {
enum Tags3 Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndInlineUnion {
enum Tags3 Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndPredefinedUnion {
enum {
TaggedUnion7tag1,
TaggedUnion7tag2,
TaggedUnion7tag3,
} Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndInlineUnion {
enum {
TaggedUnion8tag1,
TaggedUnion8tag2,
TaggedUnion8tag3,
} Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructNesting {
enum Tags3 Tag;
union {
float F;
int I;
long L;
// CHECK-MESSAGES: :[[@LINE+1]]:12: warning: tagged union has more data members (4) than tags (3)
struct innerdecl {
enum Tags3 Tag;
union Union4 Data;
} Inner;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithTypedefedTagAndTypedefedUnion {
Tags3 Tag;
Union4 Data;
};

#define DECLARE_TAGGED_UNION_STRUCT(Tag, Union, Name)\
struct Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:44: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_STRUCT(Tags3, Union4, TaggedUnionStructFromMacro);
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t

typedef enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
} Tags3;

typedef enum Tags4 {
tags4_1,
tags4_2,
tags4_3,
tags4_4,
} Tags4;

enum class Classtags3 {
classtags3_1,
classtags3_2,
classtags3_3,
};

enum class Typedtags3 : unsigned int {
typedtags3_1,
typedtags3_2,
typedtags3_3,
};

typedef union Union3 {
short *Shorts;
int *Ints;
float *Floats;
} Union3;

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} Union4;

// It is not obvious which enum is the tag for the union.
class MaybeTaggedUnion1 { // No warnings expected.
enum Tags3 TagA;
enum Tags4 TagB;
union Union4 Data;
};

// It is not obvious which union does the tag belong to.
class MaybeTaggedUnion2 { // No warnings expected.
enum Tags3 Tag;
union Union3 DataB;
union Union3 DataA;
};

// It is not obvious which union does the tag belong to.
class MaybeTaggedUnion3 { // No warnings expected.
enum Tags3 Tag;
union {
int I1;
int I2;
int I3;
};
union {
float F1;
float F2;
float F3;
};
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassPredefinedTagAndPredefinedUnion {
enum Tags3 Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassPredefinedTagAndInlineUnion {
enum Tags3 Tag;
union {
int *Ints;
char Characters[13];
class {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInlineTagAndPredefinedUnion {
enum {
tag1,
tag2,
tag3,
} Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInlineTagAndInlineUnion {
enum {
tag1,
tag2,
tag3,
} Tag;
union {
int *Ints;
char Characters[13];
class {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithNestedTaggedUnionClass {
enum Tags3 Tag;
union {
float F;
int I;
long L;
// CHECK-MESSAGES: :[[@LINE+1]]:11: warning: tagged union has more data members (4) than tags (3)
class Innerdecl {
enum Tags3 Tag;
union Union4 Data;
} Inner;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithTypedefedTag {
Tags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithEnumClass {
enum Classtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClasswithEnumClass {
enum Classtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithTypedEnum {
Typedtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithTypedEnum {
Typedtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct AnonymousTaggedUnionStruct {
Tags3 Tag;
union {
char A;
short B;
int C;
long D;
};
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithAnonymousUnion {
Tags3 Tag;
union {
char A;
short B;
int C;
long D;
};
};

namespace testnamespace {

enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructInNamespace {
Tags3 Tags;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInNamespace {
Tags3 Tags;
Union4 Data;
};

} // namespace testnamespace

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithNamespacedTagAndUnion {
testnamespace::Tags3 Tags;
testnamespace::Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithNamespacedTagAndUnion {
testnamespace::Tags3 Tags;
testnamespace::Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
struct TemplatedStructWithNamespacedTagAndUnion {
Tag Kind;
Union Data;
};

TemplatedStructWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedStruct3;

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
class TemplatedClassWithNamespacedTagAndUnion {
Tag Kind;
Union Data;
};

TemplatedClassWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedClass3;

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
struct TemplatedStruct {
Tag Kind;
Union Data;
};

TemplatedStruct<Tags3, Union3> TemplatedStruct1; // No warning expected
TemplatedStruct<Tags3, Union4> TemplatedStruct2;

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
class TemplatedClass {
Tag Kind;
Union Data;
};

TemplatedClass<Tags3, Union3> TemplatedClass1; // No warning expected
TemplatedClass<Tags3, Union4> TemplatedClass2;

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename T>
struct TemplatedStructButTaggedUnionPartIsNotTemplated {
Tags3 Kind;
Union4 Data;
T SomethingElse;
};

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename T>
class TemplatedClassButTaggedUnionPartIsNotTemplated {
Tags3 Kind;
Union4 Data;
T SomethingElse;
};

#define DECLARE_TAGGED_UNION_STRUCT(Tag, Union, Name)\
struct Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:44: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_STRUCT(Tags3, Union4, TaggedUnionStructFromMacro);

#define DECLARE_TAGGED_UNION_CLASS(Tag, Union, Name)\
class Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:43: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_CLASS(Tags3, Union4, TaggedUnionClassFromMacro);

// Lambdas implicitly compile down to an unnamed CXXRecordDecl and if they have captures,
// then those become unnamed fields.
void DoNotMatchLambdas() {
enum {
A
} e;
union {
long A;
char B;
} u;
auto L = [e, u] () {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -980,3 +980,30 @@ namespace PR78381 {
}
}
}

namespace GH109083 {
void test() {
const int N = 6;
int Arr[N] = {1, 2, 3, 4, 5, 6};

for (int I = 0; I < N; ++I) {
auto V = [T = Arr[I]]() {};
}
// CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead [modernize-loop-convert]
// CHECK-FIXES: for (int I : Arr)
// CHECK-FIXES-NEXT: auto V = [T = I]() {};
for (int I = 0; I < N; ++I) {
auto V = [T = 10 + Arr[I]]() {};
}
// CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use range-based for loop instead [modernize-loop-convert]
// CHECK-FIXES: for (int I : Arr)
// CHECK-FIXES-NEXT: auto V = [T = 10 + I]() {};

for (int I = 0; I < N; ++I) {
auto V = [T = I]() {};
}
for (int I = 0; I < N; ++I) {
auto V = [T = I + 10]() {};
}
}
} // namespace GH109083
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// RUN: %check_clang_tidy \
// RUN: -std=c++20 %s modernize-use-std-format %t -- \
// RUN: -config="{CheckOptions: {StrictMode: true}}" \
// RUN: -- -isystem %clang_tidy_headers
// RUN: -- -isystem %clang_tidy_headers \
// RUN: -DPRI_CMDLINE_MACRO="\"s\"" \
// RUN: -D__PRI_CMDLINE_MACRO="\"s\""
// RUN: %check_clang_tidy \
// RUN: -std=c++20 %s modernize-use-std-format %t -- \
// RUN: -config="{CheckOptions: {StrictMode: false}}" \
// RUN: -- -isystem %clang_tidy_headers
// RUN: -- -isystem %clang_tidy_headers \
// RUN: -DPRI_CMDLINE_MACRO="\"s\"" \
// RUN: -D__PRI_CMDLINE_MACRO="\"s\""
#include <string>
// CHECK-FIXES: #include <format>
#include <inttypes.h>

namespace absl
{
Expand Down Expand Up @@ -102,18 +107,74 @@ std::string StrFormat_macros() {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
// CHECK-FIXES: std::format("Hello {}", 42);

// The format string is replaced even though it comes from a macro, this
// behaviour is required so that that <inttypes.h> macros are replaced.
#define FORMAT_STRING "Hello %s"
auto s2 = absl::StrFormat(FORMAT_STRING, 42);
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
// CHECK-FIXES: std::format("Hello {}", 42);

// Arguments that are macros aren't replaced with their value, even if they are rearranged.
#define VALUE 3.14159265358979323846
#define WIDTH 10
#define PRECISION 4
auto s3 = absl::StrFormat("Hello %*.*f", WIDTH, PRECISION, VALUE);
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
// CHECK-FIXES: std::format("Hello {:{}.{}f}", VALUE, WIDTH, PRECISION);

const uint64_t u64 = 42;
const uint32_t u32 = 32;
std::string s;

auto s4 = absl::StrFormat("Replaceable macro at end %" PRIu64, u64);
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
// CHECK-FIXES: std::format("Replaceable macro at end {}", u64);

auto s5 = absl::StrFormat("Replaceable macros in middle %" PRIu64 " %" PRIu32 "\n", u64, u32);
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
// CHECK-FIXES: std::format("Replaceable macros in middle {} {}\n", u64, u32);

// These need PRI and __PRI prefixes so that the check get as far as looking for
// where the macro comes from.
#define PRI_FMT_MACRO "s"
#define __PRI_FMT_MACRO "s"

auto s6 = absl::StrFormat("Unreplaceable macro at end %" PRI_FMT_MACRO, s.c_str());
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-format]

auto s7 = absl::StrFormat(__PRI_FMT_MACRO " Unreplaceable macro at beginning %s", s);
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro '__PRI_FMT_MACRO' [modernize-use-std-format]

auto s8 = absl::StrFormat("Unreplacemable macro %" PRI_FMT_MACRO " in the middle", s);
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-format]

auto s9 = absl::StrFormat("First macro is replaceable %" PRIu64 " but second one is not %" __PRI_FMT_MACRO, u64, s);
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro '__PRI_FMT_MACRO' [modernize-use-std-format]

// Needs a PRI prefix so that we get as far as looking for where the macro comes from
auto s10 = absl::StrFormat(" macro from command line %" PRI_CMDLINE_MACRO, s);
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_CMDLINE_MACRO' [modernize-use-std-format]

// Needs a __PRI prefix so that we get as far as looking for where the macro comes from
auto s11 = absl::StrFormat(" macro from command line %" __PRI_CMDLINE_MACRO, s);
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro '__PRI_CMDLINE_MACRO' [modernize-use-std-format]

// We ought to be able to fix this since the macro surrounds the whole call
// and therefore can't change the format string independently. This is
// required to be able to fix calls inside Catch2 macros for example.
#define SURROUND_ALL(x) x
auto s12 = SURROUND_ALL(absl::StrFormat("Macro surrounding entire invocation %" PRIu64, u64));
// CHECK-MESSAGES: [[@LINE-1]]:27: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
// CHECK-FIXES: auto s12 = SURROUND_ALL(std::format("Macro surrounding entire invocation {}", u64));

// But having that surrounding macro shouldn't stop us ignoring an
// unreplaceable macro elsewhere.
auto s13 = SURROUND_ALL(absl::StrFormat("Macro surrounding entire invocation with unreplaceable macro %" PRI_FMT_MACRO, s));
// CHECK-MESSAGES: [[@LINE-1]]:27: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-format]

// At the moment at least the check will replace occurrences where the
// function name is the result of expanding a macro.
#define SURROUND_FUNCTION_NAME(x) absl:: x
auto s14 = SURROUND_FUNCTION_NAME(StrFormat)("Hello %d", 4442);
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: use 'std::format' instead of 'StrFormat' [modernize-use-std-format]
// CHECK-FIXES: auto s14 = std::format("Hello {}", 4442);

// We can't safely fix occurrences where the macro may affect the format
// string differently in different builds.
#define SURROUND_FORMAT(x) "!" x
auto s15 = absl::StrFormat(SURROUND_FORMAT("Hello %d"), 4443);
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: unable to use 'std::format' instead of 'StrFormat' because format string contains unreplaceable macro 'SURROUND_FORMAT' [modernize-use-std-format]
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// RUN: %check_clang_tidy -check-suffixes=,STRICT \
// RUN: -std=c++23 %s modernize-use-std-print %t -- \
// RUN: -config="{CheckOptions: {StrictMode: true}}" \
// RUN: -- -isystem %clang_tidy_headers -fexceptions
// RUN: -- -isystem %clang_tidy_headers -fexceptions \
// RUN: -DPRI_CMDLINE_MACRO="\"s\"" \
// RUN: -D__PRI_CMDLINE_MACRO="\"s\""
// RUN: %check_clang_tidy -check-suffixes=,NOTSTRICT \
// RUN: -std=c++23 %s modernize-use-std-print %t -- \
// RUN: -config="{CheckOptions: {StrictMode: false}}" \
// RUN: -- -isystem %clang_tidy_headers -fexceptions
// RUN: -- -isystem %clang_tidy_headers -fexceptions \
// RUN: -DPRI_CMDLINE_MACRO="\"s\"" \
// RUN: -D__PRI_CMDLINE_MACRO="\"s\""
#include <cstddef>
#include <cstdint>
#include <cstdio>
Expand Down Expand Up @@ -1571,3 +1575,68 @@ void p(S s1, S *s2)
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
// CHECK-FIXES: std::print("Not std::string {} {}", s1.data(), s2->data());
}

// These need PRI and __PRI prefixes so that the check gets as far as looking
// for where the macro comes from.
#define PRI_FMT_MACRO "s"
#define __PRI_FMT_MACRO "s"

void macro_expansion(const char *s)
{
const uint64_t u64 = 42;
const uint32_t u32 = 32;

printf("Replaceable macro at end %" PRIu64, u64);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
// CHECK-FIXES: std::print("Replaceable macro at end {}", u64);

printf("Replaceable macros in middle %" PRIu64 " %" PRIu32 "\n", u64, u32);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
// CHECK-FIXES: std::println("Replaceable macros in middle {} {}", u64, u32);

printf("Unreplaceable macro at end %" PRI_FMT_MACRO, s);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-print]

printf(PRI_FMT_MACRO " Unreplaceable macro at beginning %s", s);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-print]

printf("Unreplacemable macro %" __PRI_FMT_MACRO " in the middle", s);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro '__PRI_FMT_MACRO' [modernize-use-std-print]

printf("First macro is replaceable %" PRIu64 " but second one is not %" PRI_FMT_MACRO, u64, s);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-print]

// Needs a PRI prefix so that we get as far as looking for where the macro comes from
printf(" macro from command line %" PRI_CMDLINE_MACRO, s);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro 'PRI_CMDLINE_MACRO' [modernize-use-std-print]

// Needs a __PRI prefix so that we get as far as looking for where the macro comes from
printf(" macro from command line %" __PRI_CMDLINE_MACRO, s);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro '__PRI_CMDLINE_MACRO' [modernize-use-std-print]

// We ought to be able to fix this since the macro surrounds the whole call
// and therefore can't change the format string independently. This is
// required to be able to fix calls inside Catch2 macros for example.
#define SURROUND_ALL(x) x
SURROUND_ALL(printf("Macro surrounding entire invocation %" PRIu64, u64));
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
// CHECK-FIXES: SURROUND_ALL(std::print("Macro surrounding entire invocation {}", u64));

// But having that surrounding macro shouldn't stop us ignoring an
// unreplaceable macro elsewhere.
SURROUND_ALL(printf("Macro surrounding entire invocation with unreplaceable macro %" PRI_FMT_MACRO, s));
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro 'PRI_FMT_MACRO' [modernize-use-std-print]

// At the moment at least the check will replace occurrences where the
// function name is the result of expanding a macro.
#define SURROUND_FUNCTION_NAME(x) x
SURROUND_FUNCTION_NAME(printf)("Hello %d", 4442);
// CHECK-MESSAGES: [[@LINE-1]]:26: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
// CHECK-FIXES: std::print("Hello {}", 4442);

// We can't safely fix occurrences where the macro may affect the format
// string differently in different builds.
#define SURROUND_FORMAT(x) "!" x
printf(SURROUND_FORMAT("Hello %d"), 4443);
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::print' instead of 'printf' because format string contains unreplaceable macro 'SURROUND_FORMAT' [modernize-use-std-print]
}
Original file line number Diff line number Diff line change
Expand Up @@ -546,3 +546,17 @@ void testAlsoNonMoveable() {
}

} // namespace issue_62550

namespace GH111450 {
struct Status;

struct Error {
Error(const Status& S);
};

struct Result {
Error E;
Result(Status&& S) : E(std::move(S)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: passing result of std::move() as a const reference argument; no move will actually happen [performance-move-const-arg]
};
} // namespace GH111450
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@
// CHECK-VERIFY-BLOCK-BAD: command-line option '-config': warning: check glob 'bugprone-arguments-*' doesn't match any known check [-verify-config]
// CHECK-VERIFY-BLOCK-BAD: command-line option '-config': warning: unknown check 'bugprone-assert-side-effects'; did you mean 'bugprone-assert-side-effect' [-verify-config]

// RUN: echo -e 'Checks: "-*,clang-analyzer-optin.cplusplus.UninitializedObject"\nCheckOptions:\n clang-analyzer-optin.cplusplus.UninitializedObject:Pedantic: true' > %T/MyClangTidyConfigCSA
// RUN: clang-tidy --verify-config --config-file=%T/MyClangTidyConfigCSA 2>&1 | FileCheck %s -check-prefix=CHECK-VERIFY-CSA-OK -implicit-check-not='{{warnings|error}}'
// CHECK-VERIFY-CSA-OK: No config errors detected.

// RUN: echo -e 'Checks: "-*,clang-analyzer-optin.cplusplus.UninitializedObject"\nCheckOptions:\n clang-analyzer-optin.cplusplus.UninitializedObject.Pedantic: true' > %T/MyClangTidyConfigCSABad
// RUN: not clang-tidy --verify-config --config-file=%T/MyClangTidyConfigCSABad 2>&1 | FileCheck %s -check-prefix=CHECK-VERIFY-CSA-BAD -implicit-check-not='{{warnings|error}}'
// CHECK-VERIFY-CSA-BAD: command-line option '-config': warning: unknown check option 'clang-analyzer-optin.cplusplus.UninitializedObject.Pedantic'; did you mean 'clang-analyzer-optin.cplusplus.UninitializedObject:Pedantic' [-verify-config]

44 changes: 20 additions & 24 deletions clang/CodeOwners.rst → clang/Maintainers.rst
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
=================
Clang Code Owners
Clang Maintainers
=================

This file is a list of the
`code owners <https://llvm.org/docs/DeveloperPolicy.html#code-owners>`_ for
`maintainers <https://llvm.org/docs/DeveloperPolicy.html#maintainers>`_ for
Clang.

.. contents::
:depth: 2
:local:

Current Code Owners
===================
The following people are the active code owners for the project. Please reach
Active Maintainers
==================
The following people are the active maintainers for the project. Please reach
out to them for code reviews, questions about their area of expertise, or other
assistance.

All parts of Clang not covered by someone else
----------------------------------------------
Lead Maintainer
---------------
| Aaron Ballman
| aaron\@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord), AaronBallman (IRC)

Contained Components
--------------------
These code owners are responsible for particular high-level components within
These maintainers are responsible for particular high-level components within
Clang that are typically contained to one area of the compiler.

AST matchers
~~~~~~~~~~~~
| Manuel Klimek
| klimek\@google.com (email), klimek (Phabricator), r4nt (GitHub)
| Aaron Ballman
| aaron\@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord), AaronBallman (IRC)

Clang LLVM IR generation
Expand Down Expand Up @@ -60,7 +60,7 @@ Analysis & CFG
Experimental new constant interpreter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Timm Bäder
| tbaeder\@redhat.com (email), tbaeder (Phabricator), tbaederr (GitHub), tbaeder (Discourse), tbaeder (Discord)
| tbaeder\@redhat.com (em), tbaeder (Phabricator), tbaederr (GitHub), tbaeder (Discourse), tbaeder (Discord)

Modules & serialization
Expand Down Expand Up @@ -125,14 +125,9 @@ Driver parts not covered by someone else

Tools
-----
These code owners are responsible for user-facing tools under the Clang
These maintainers are responsible for user-facing tools under the Clang
umbrella or components used to support such tools.

Tooling library
~~~~~~~~~~~~~~~
| Manuel Klimek
| klimek\@google.com (email), klimek (Phabricator), r4nt (GitHub)

clang-format
~~~~~~~~~~~~
Expand Down Expand Up @@ -255,19 +250,20 @@ SYCL conformance
| alexey.bader\@intel.com (email), bader (Phabricator), bader (GitHub)

Former Code Owners
==================
The following people have graciously spent time performing code ownership
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!

Emeritus owners
---------------
Emeritus Lead Maintainers
-------------------------
| Doug Gregor (dgregor\@apple.com)
| Richard Smith (richard\@metafoo.co.uk)

Former component owners
-----------------------
Inactive component maintainers
------------------------------
| Chandler Carruth (chandlerc\@gmail.com, chandlerc\@google.com) -- CMake, library layering
| Devin Coughlin (dcoughlin\@apple.com) -- Clang static analyzer
| Manuel Klimek (klimek\@google.com (email), klimek (Phabricator), r4nt (GitHub)) -- Tooling, AST matchers
1 change: 0 additions & 1 deletion clang/cmake/caches/Android.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ set(CLANG_VENDOR Android CACHE STRING "")

set(CMAKE_BUILD_TYPE RELEASE CACHE STRING "")

set(HAVE_LIBCXXABI ON CACHE BOOL "")
set(LLVM_BUILD_TOOLS OFF CACHE BOOL "")
set(LLVM_ENABLE_ASSERTIONS ON CACHE BOOL "")
set(LLVM_ENABLE_THREADS OFF CACHE BOOL "")
Expand Down
3 changes: 3 additions & 0 deletions clang/cmake/caches/Release.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ set_final_stage_var(LLVM_ENABLE_PROJECTS "${LLVM_RELEASE_ENABLE_PROJECTS}" STRIN
set_final_stage_var(CPACK_GENERATOR "TXZ" STRING)
set_final_stage_var(CPACK_ARCHIVE_THREADS "0" STRING)

if(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Darwin")
set_final_stage_var(LLVM_USE_STATIC_ZSTD "ON" BOOL)
endif()
15 changes: 15 additions & 0 deletions clang/cmake/caches/hexagon-unknown-linux-musl-clang-cross.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file is for the llvm+clang options that are specific to building
# a cross-toolchain targeting hexagon linux.
set(DEFAULT_SYSROOT "../target/hexagon-unknown-linux-musl/" CACHE STRING "")
set(CLANG_LINKS_TO_CREATE
hexagon-linux-musl-clang++
hexagon-linux-musl-clang
hexagon-unknown-linux-musl-clang++
hexagon-unknown-linux-musl-clang
hexagon-none-elf-clang++
hexagon-none-elf-clang
hexagon-unknown-none-elf-clang++
hexagon-unknown-none-elf-clang
CACHE STRING "")

set(LLVM_INSTALL_TOOLCHAIN_ONLY ON CACHE BOOL "")
15 changes: 15 additions & 0 deletions clang/cmake/caches/hexagon-unknown-linux-musl-clang.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

set(LLVM_TARGETS_TO_BUILD "Hexagon" CACHE STRING "")
set(LLVM_DEFAULT_TARGET_TRIPLE "hexagon-unknown-linux-musl" CACHE STRING "")
set(CLANG_DEFAULT_CXX_STDLIB "libc++" CACHE STRING "")
set(CLANG_DEFAULT_OBJCOPY "llvm-objcopy" CACHE STRING "")
set(CLANG_DEFAULT_RTLIB "compiler-rt" CACHE STRING "")
set(CLANG_DEFAULT_UNWINDLIB "libunwind" CACHE STRING "")
set(CLANG_DEFAULT_LINKER "lld" CACHE STRING "")
set(LLVM_ENABLE_PROJECTS "clang;lld" CACHE STRING "")

set(LLVM_INCLUDE_TESTS OFF CACHE BOOL "")
set(LLVM_INCLUDE_DOCS OFF CACHE BOOL "")
# Enabling toolchain-only causes problems when doing some of the
# subsequent builds, will need to investigate:
set(LLVM_INSTALL_TOOLCHAIN_ONLY OFF CACHE BOOL "")
8 changes: 8 additions & 0 deletions clang/docs/AddressSanitizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,14 @@ Limitations
usually expected.
* Static linking of executables is not supported.

Security Considerations
=======================

AddressSanitizer is a bug detection tool and its runtime is not meant to be
linked against production executables. While it may be useful for testing,
AddressSanitizer's runtime was not developed with security-sensitive
constraints in mind and may compromise the security of the resulting executable.

Supported Platforms
===================

Expand Down
2 changes: 1 addition & 1 deletion clang/docs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ if (LLVM_ENABLE_SPHINX)
"${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}"

COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/../CodeOwners.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/../Maintainers.rst"
"${CMAKE_CURRENT_BINARY_DIR}"
)

Expand Down
193 changes: 157 additions & 36 deletions clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ the configuration (without a prefix: ``Auto``).
.. _AlignArrayOfStructures:

**AlignArrayOfStructures** (``ArrayInitializerAlignmentStyle``) :versionbadge:`clang-format 13` :ref:`¶ <AlignArrayOfStructures>`
if not ``None``, when using initialization for an array of structs
If not ``None``, when using initialization for an array of structs
aligns the fields into columns.


Expand Down Expand Up @@ -307,11 +307,12 @@ the configuration (without a prefix: ``Auto``).
Alignment options.

They can also be read as a whole for compatibility. The choices are:
- None
- Consecutive
- AcrossEmptyLines
- AcrossComments
- AcrossEmptyLinesAndComments

* ``None``
* ``Consecutive``
* ``AcrossEmptyLines``
* ``AcrossComments``
* ``AcrossEmptyLinesAndComments``

For example, to align across empty lines and not across comments, either
of these work.
Expand Down Expand Up @@ -392,6 +393,21 @@ the configuration (without a prefix: ``Auto``).
a &= 2;
bbb = 2;

* ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
are aligned.

.. code-block:: c++

true:
unsigned int f1(void);
void f2(void);
size_t f3(void);

false:
unsigned int f1(void);
void f2(void);
size_t f3(void);

* ``bool AlignFunctionPointers`` Only for ``AlignConsecutiveDeclarations``. Whether function pointers are
aligned.

Expand Down Expand Up @@ -449,11 +465,12 @@ the configuration (without a prefix: ``Auto``).
Alignment options.

They can also be read as a whole for compatibility. The choices are:
- None
- Consecutive
- AcrossEmptyLines
- AcrossComments
- AcrossEmptyLinesAndComments

* ``None``
* ``Consecutive``
* ``AcrossEmptyLines``
* ``AcrossComments``
* ``AcrossEmptyLinesAndComments``

For example, to align across empty lines and not across comments, either
of these work.
Expand Down Expand Up @@ -534,6 +551,21 @@ the configuration (without a prefix: ``Auto``).
a &= 2;
bbb = 2;

* ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
are aligned.

.. code-block:: c++

true:
unsigned int f1(void);
void f2(void);
size_t f3(void);

false:
unsigned int f1(void);
void f2(void);
size_t f3(void);

* ``bool AlignFunctionPointers`` Only for ``AlignConsecutiveDeclarations``. Whether function pointers are
aligned.

Expand Down Expand Up @@ -591,11 +623,12 @@ the configuration (without a prefix: ``Auto``).
Alignment options.

They can also be read as a whole for compatibility. The choices are:
- None
- Consecutive
- AcrossEmptyLines
- AcrossComments
- AcrossEmptyLinesAndComments

* ``None``
* ``Consecutive``
* ``AcrossEmptyLines``
* ``AcrossComments``
* ``AcrossEmptyLinesAndComments``

For example, to align across empty lines and not across comments, either
of these work.
Expand Down Expand Up @@ -676,6 +709,21 @@ the configuration (without a prefix: ``Auto``).
a &= 2;
bbb = 2;

* ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
are aligned.

.. code-block:: c++

true:
unsigned int f1(void);
void f2(void);
size_t f3(void);

false:
unsigned int f1(void);
void f2(void);
size_t f3(void);

* ``bool AlignFunctionPointers`` Only for ``AlignConsecutiveDeclarations``. Whether function pointers are
aligned.

Expand Down Expand Up @@ -734,11 +782,12 @@ the configuration (without a prefix: ``Auto``).
Alignment options.

They can also be read as a whole for compatibility. The choices are:
- None
- Consecutive
- AcrossEmptyLines
- AcrossComments
- AcrossEmptyLinesAndComments

* ``None``
* ``Consecutive``
* ``AcrossEmptyLines``
* ``AcrossComments``
* ``AcrossEmptyLinesAndComments``

For example, to align across empty lines and not across comments, either
of these work.
Expand Down Expand Up @@ -819,6 +868,21 @@ the configuration (without a prefix: ``Auto``).
a &= 2;
bbb = 2;

* ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
are aligned.

.. code-block:: c++

true:
unsigned int f1(void);
void f2(void);
size_t f3(void);

false:
unsigned int f1(void);
void f2(void);
size_t f3(void);

* ``bool AlignFunctionPointers`` Only for ``AlignConsecutiveDeclarations``. Whether function pointers are
aligned.

Expand Down Expand Up @@ -996,11 +1060,12 @@ the configuration (without a prefix: ``Auto``).
Alignment options.

They can also be read as a whole for compatibility. The choices are:
- None
- Consecutive
- AcrossEmptyLines
- AcrossComments
- AcrossEmptyLinesAndComments

* ``None``
* ``Consecutive``
* ``AcrossEmptyLines``
* ``AcrossComments``
* ``AcrossEmptyLinesAndComments``

For example, to align across empty lines and not across comments, either
of these work.
Expand Down Expand Up @@ -1081,6 +1146,21 @@ the configuration (without a prefix: ``Auto``).
a &= 2;
bbb = 2;

* ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
are aligned.

.. code-block:: c++

true:
unsigned int f1(void);
void f2(void);
size_t f3(void);

false:
unsigned int f1(void);
void f2(void);
size_t f3(void);

* ``bool AlignFunctionPointers`` Only for ``AlignConsecutiveDeclarations``. Whether function pointers are
aligned.

Expand Down Expand Up @@ -1136,11 +1216,12 @@ the configuration (without a prefix: ``Auto``).
Alignment options.

They can also be read as a whole for compatibility. The choices are:
- None
- Consecutive
- AcrossEmptyLines
- AcrossComments
- AcrossEmptyLinesAndComments

* ``None``
* ``Consecutive``
* ``AcrossEmptyLines``
* ``AcrossComments``
* ``AcrossEmptyLinesAndComments``

For example, to align across empty lines and not across comments, either
of these work.
Expand Down Expand Up @@ -1221,6 +1302,21 @@ the configuration (without a prefix: ``Auto``).
a &= 2;
bbb = 2;

* ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
are aligned.

.. code-block:: c++

true:
unsigned int f1(void);
void f2(void);
size_t f3(void);

false:
unsigned int f1(void);
void f2(void);
size_t f3(void);

* ``bool AlignFunctionPointers`` Only for ``AlignConsecutiveDeclarations``. Whether function pointers are
aligned.

Expand Down Expand Up @@ -1276,11 +1372,12 @@ the configuration (without a prefix: ``Auto``).
Alignment options.

They can also be read as a whole for compatibility. The choices are:
- None
- Consecutive
- AcrossEmptyLines
- AcrossComments
- AcrossEmptyLinesAndComments

* ``None``
* ``Consecutive``
* ``AcrossEmptyLines``
* ``AcrossComments``
* ``AcrossEmptyLinesAndComments``

For example, to align across empty lines and not across comments, either
of these work.
Expand Down Expand Up @@ -1361,6 +1458,21 @@ the configuration (without a prefix: ``Auto``).
a &= 2;
bbb = 2;

* ``bool AlignFunctionDeclarations`` Only for ``AlignConsecutiveDeclarations``. Whether function declarations
are aligned.

.. code-block:: c++

true:
unsigned int f1(void);
void f2(void);
size_t f3(void);

false:
unsigned int f1(void);
void f2(void);
size_t f3(void);

* ``bool AlignFunctionPointers`` Only for ``AlignConsecutiveDeclarations``. Whether function pointers are
aligned.

Expand Down Expand Up @@ -6554,6 +6666,15 @@ the configuration (without a prefix: ``Auto``).
let DAGArgOtherID = (other i32:$other1, i32:$other2);
let DAGArgBang = (!cast<SomeType>("Some") i32:$src1, i32:$src2)

.. _TemplateNames:

**TemplateNames** (``List of Strings``) :versionbadge:`clang-format 20` :ref:`¶ <TemplateNames>`
A vector of non-keyword identifiers that should be interpreted as
template names.

A ``<`` after a template name is annotated as a template opener instead of
a binary operator.

.. _TypeNames:

**TypeNames** (``List of Strings``) :versionbadge:`clang-format 17` :ref:`¶ <TypeNames>`
Expand Down
1 change: 0 additions & 1 deletion clang/docs/CodeOwners.rst

This file was deleted.

8 changes: 8 additions & 0 deletions clang/docs/HardwareAssistedAddressSanitizerDesign.rst
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,14 @@ than that of AddressSanitizer:
`1/TG` extra memory for the shadow
and some overhead due to `TG`-aligning all objects.

Security Considerations
=======================

HWASAN is a bug detection tool and its runtime is not meant to be
linked against production executables. While it may be useful for testing,
HWASAN's runtime was not developed with security-sensitive
constraints in mind and may compromise the security of the resulting executable.

Supported architectures
=======================
HWASAN relies on `Address Tagging`_ which is only available on AArch64.
Expand Down
165 changes: 94 additions & 71 deletions clang/docs/LanguageExtensions.rst

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions clang/docs/LeakSanitizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ To use LeakSanitizer in stand-alone mode, link your program with
link step, so that it would link in proper LeakSanitizer run-time library
into the final executable.

Security Considerations
=======================

LeakSanitizer is a bug detection tool and its runtime is not meant to be
linked against production executables. While it may be useful for testing,
LeakSanitizer's runtime was not developed with security-sensitive
constraints in mind and may compromise the security of the resulting executable.

Supported Platforms
===================

Expand Down
Loading