diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 689eb92a3d8d..642f025359b1 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -78,6 +78,7 @@ #include "SuspiciousStringviewDataUsageCheck.h" #include "SwappedArgumentsCheck.h" #include "SwitchMissingDefaultCaseCheck.h" +#include "TaggedUnionMemberCountCheck.h" #include "TerminatingContinueCheck.h" #include "ThrowKeywordMissingCheck.h" #include "TooSmallLoopVariableCheck.h" @@ -229,6 +230,8 @@ public: "bugprone-suspicious-stringview-data-usage"); CheckFactories.registerCheck( "bugprone-swapped-arguments"); + CheckFactories.registerCheck( + "bugprone-tagged-union-member-count"); CheckFactories.registerCheck( "bugprone-terminating-continue"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt index cb0d8ae98bac..9f7ecb9623c5 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -73,6 +73,7 @@ add_clang_library(clangTidyBugproneModule SuspiciousSemicolonCheck.cpp SuspiciousStringCompareCheck.cpp SwappedArgumentsCheck.cpp + TaggedUnionMemberCountCheck.cpp TerminatingContinueCheck.cpp ThrowKeywordMissingCheck.cpp TooSmallLoopVariableCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp new file mode 100644 index 000000000000..db99ef3786e5 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp @@ -0,0 +1,199 @@ +//===--- TaggedUnionMemberCountCheck.cpp - clang-tidy ---------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "TaggedUnionMemberCountCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallSet.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::bugprone { + +static constexpr llvm::StringLiteral StrictModeOptionName = "StrictMode"; +static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName = + "EnableCountingEnumHeuristic"; +static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName = + "CountingEnumPrefixes"; +static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName = + "CountingEnumSuffixes"; + +static constexpr bool StrictModeOptionDefaultValue = false; +static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue = true; +static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue = + ""; +static constexpr llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue = + "count"; + +static constexpr llvm::StringLiteral RootMatchBindName = "root"; +static constexpr llvm::StringLiteral UnionMatchBindName = "union"; +static constexpr llvm::StringLiteral TagMatchBindName = "tags"; + +namespace { + +AST_MATCHER_P2(RecordDecl, fieldCountOfKindIsOne, + ast_matchers::internal::Matcher, InnerMatcher, + StringRef, BindName) { + // BoundNodesTreeBuilder resets itself when a match occurs. + // So to avoid losing previously saved binds, a temporary instance + // is used for matching. + // + // For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a76559 + clang::ast_matchers::internal::BoundNodesTreeBuilder TempBuilder; + + const FieldDecl *FirstMatch = nullptr; + for (const FieldDecl *Field : Node.fields()) { + if (InnerMatcher.matches(*Field, Finder, &TempBuilder)) { + if (FirstMatch) { + return false; + } else { + FirstMatch = Field; + } + } + } + + if (FirstMatch) { + Builder->setBinding(BindName, clang::DynTypedNode::create(*FirstMatch)); + return true; + } + return false; +} + +} // namespace + +TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StrictMode( + Options.get(StrictModeOptionName, StrictModeOptionDefaultValue)), + EnableCountingEnumHeuristic( + Options.get(EnableCountingEnumHeuristicOptionName, + EnableCountingEnumHeuristicOptionDefaultValue)), + CountingEnumPrefixes(utils::options::parseStringList( + Options.get(CountingEnumPrefixesOptionName, + CountingEnumPrefixesOptionDefaultValue))), + CountingEnumSuffixes(utils::options::parseStringList( + Options.get(CountingEnumSuffixesOptionName, + CountingEnumSuffixesOptionDefaultValue))) { + if (!EnableCountingEnumHeuristic) { + if (Options.get(CountingEnumPrefixesOptionName)) + configurationDiag("%0: Counting enum heuristic is disabled but " + "%1 is set") + << Name << CountingEnumPrefixesOptionName; + if (Options.get(CountingEnumSuffixesOptionName)) + configurationDiag("%0: Counting enum heuristic is disabled but " + "%1 is set") + << Name << CountingEnumSuffixesOptionName; + } +} + +void TaggedUnionMemberCountCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, StrictModeOptionName, StrictMode); + Options.store(Opts, EnableCountingEnumHeuristicOptionName, + EnableCountingEnumHeuristic); + Options.store(Opts, CountingEnumPrefixesOptionName, + utils::options::serializeStringList(CountingEnumPrefixes)); + Options.store(Opts, CountingEnumSuffixesOptionName, + utils::options::serializeStringList(CountingEnumSuffixes)); +} + +void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) { + + auto UnionField = fieldDecl(hasType(qualType( + hasCanonicalType(recordType(hasDeclaration(recordDecl(isUnion()))))))); + + auto EnumField = fieldDecl(hasType( + qualType(hasCanonicalType(enumType(hasDeclaration(enumDecl())))))); + + auto hasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName); + auto hasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName); + + Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), hasOneUnionField, + hasOneEnumField, unless(isImplicit())) + .bind(RootMatchBindName), + this); +} + +bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const { + if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool { + return Name.starts_with_insensitive(Prefix); + })) + return true; + if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool { + return Name.ends_with_insensitive(Suffix); + })) + return true; + return false; +} + +std::pair +TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) { + llvm::SmallSet EnumValues; + + const EnumConstantDecl *LastEnumConstant = nullptr; + for (const EnumConstantDecl *Enumerator : ED->enumerators()) { + EnumValues.insert(Enumerator->getInitVal()); + LastEnumConstant = Enumerator; + } + + if (EnableCountingEnumHeuristic && LastEnumConstant && + isCountingEnumLikeName(LastEnumConstant->getName()) && + (LastEnumConstant->getInitVal() == (EnumValues.size() - 1))) { + return {EnumValues.size() - 1, LastEnumConstant}; + } + + return {EnumValues.size(), nullptr}; +} + +void TaggedUnionMemberCountCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Root = Result.Nodes.getNodeAs(RootMatchBindName); + const auto *UnionField = + Result.Nodes.getNodeAs(UnionMatchBindName); + const auto *TagField = Result.Nodes.getNodeAs(TagMatchBindName); + + assert(Root && "Root is missing!"); + assert(UnionField && "UnionField is missing!"); + assert(TagField && "TagField is missing!"); + if (!Root || !UnionField || !TagField) + return; + + const auto *UnionDef = + UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl(); + const auto *EnumDef = llvm::dyn_cast( + TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl()); + + assert(UnionDef && "UnionDef is missing!"); + assert(EnumDef && "EnumDef is missing!"); + if (!UnionDef || !EnumDef) + return; + + const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields()); + auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef); + + if (UnionMemberCount > TagCount) { + diag(Root->getLocation(), + "tagged union has more data members (%0) than tags (%1)!") + << UnionMemberCount << TagCount; + } else if (StrictMode && UnionMemberCount < TagCount) { + diag(Root->getLocation(), + "tagged union has fewer data members (%0) than tags (%1)!") + << UnionMemberCount << TagCount; + } + + if (CountingEnumConstantDecl) { + diag(CountingEnumConstantDecl->getLocation(), + "assuming that this constant is just an auxiliary value and not " + "used for indicating a valid union data member", + DiagnosticIDs::Note); + } +} + +} // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h new file mode 100644 index 000000000000..8b9d677d00b4 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h @@ -0,0 +1,41 @@ +//===--- TaggedUnionMemberCountCheck.h - clang-tidy -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::bugprone { + +/// Gives warnings for tagged unions, where the number of tags is +/// different from the number of data members inside the union. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html +class TaggedUnionMemberCountCheck : public ClangTidyCheck { +public: + TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool StrictMode; + const bool EnableCountingEnumHeuristic; + const std::vector CountingEnumPrefixes; + const std::vector CountingEnumSuffixes; + + std::pair + getNumberOfEnumValues(const EnumDecl *ED); + bool isCountingEnumLikeName(StringRef Name) const; +}; + +} // namespace clang::tidy::bugprone + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 7d37a4b03222..e34e296b5a09 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -103,6 +103,12 @@ Improvements to clang-tidy New checks ^^^^^^^^^^ +- New :doc:`bugprone-tagged-union-member-count + ` check. + + Gives warnings for tagged unions, where the number of tags is + different from the number of data members inside the union. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst new file mode 100644 index 000000000000..2f1036c10345 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst @@ -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; + }; diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 1909d7b8d8e2..e3dfabba8fad 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -145,6 +145,7 @@ Clang-Tidy Checks :doc:`bugprone-suspicious-stringview-data-usage `, :doc:`bugprone-swapped-arguments `, "Yes" :doc:`bugprone-switch-missing-default-case `, + :doc:`bugprone-tagged-union-member-count `, :doc:`bugprone-terminating-continue `, "Yes" :doc:`bugprone-throw-keyword-missing `, :doc:`bugprone-too-small-loop-variable `, diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-bad-config.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-bad-config.cpp new file mode 100644 index 000000000000..73bfb7acbc46 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-bad-config.cpp @@ -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 diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-disabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-disabled.cpp new file mode 100644 index 000000000000..dca52170a749 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-disabled.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-enabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-enabled.cpp new file mode 100644 index 000000000000..96aef122e85e --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-enabled.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes-and-suffixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes-and-suffixes.cpp new file mode 100644 index 000000000000..c0e33ac6f6f3 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes-and-suffixes.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes.cpp new file mode 100644 index 000000000000..c287b1953a33 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-suffixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-suffixes.cpp new file mode 100644 index 000000000000..f248f2efaa5a --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-suffixes.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-disabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-disabled.cpp new file mode 100644 index 000000000000..c39683c3c40f --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-disabled.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-enabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-enabled.cpp new file mode 100644 index 000000000000..10d376d79196 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-enabled.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.c new file mode 100644 index 000000000000..60c93c553bac --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.c @@ -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); diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp new file mode 100644 index 000000000000..25827e8c8de0 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp @@ -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 +struct TemplatedStructWithNamespacedTagAndUnion { + Tag Kind; + Union Data; +}; + +TemplatedStructWithNamespacedTagAndUnion TemplatedStruct3; + +// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3) +template +class TemplatedClassWithNamespacedTagAndUnion { + Tag Kind; + Union Data; +}; + +TemplatedClassWithNamespacedTagAndUnion TemplatedClass3; + +// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3) +template +struct TemplatedStruct { + Tag Kind; + Union Data; +}; + +TemplatedStruct TemplatedStruct1; // No warning expected +TemplatedStruct TemplatedStruct2; + +// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3) +template +class TemplatedClass { + Tag Kind; + Union Data; +}; + +TemplatedClass TemplatedClass1; // No warning expected +TemplatedClass TemplatedClass2; + +// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3) +template +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 +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] () {}; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.m b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.m new file mode 100644 index 000000000000..60c93c553bac --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.m @@ -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); diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.mm b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.mm new file mode 100644 index 000000000000..8b308555281c --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.mm @@ -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 +struct TemplatedStructWithNamespacedTagAndUnion { + Tag Kind; + Union Data; +}; + +TemplatedStructWithNamespacedTagAndUnion TemplatedStruct3; + +// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3) +template +class TemplatedClassWithNamespacedTagAndUnion { + Tag Kind; + Union Data; +}; + +TemplatedClassWithNamespacedTagAndUnion TemplatedClass3; + +// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3) +template +struct TemplatedStruct { + Tag Kind; + Union Data; +}; + +TemplatedStruct TemplatedStruct1; // No warning expected +TemplatedStruct TemplatedStruct2; + +// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3) +template +class TemplatedClass { + Tag Kind; + Union Data; +}; + +TemplatedClass TemplatedClass1; // No warning expected +TemplatedClass TemplatedClass2; + +// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3) +template +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 +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] () {}; +}