[clang-tools-extra] [clang-tidy] Add bugprone-macro-condition check (PR #210768)

Richard Thomson via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 30 16:53:57 PDT 2026


https://github.com/LegalizeAdulthood updated https://github.com/llvm/llvm-project/pull/210768

>From 2fe11bddd8c4d29132e0af01a194c82304ef234b Mon Sep 17 00:00:00 2001
From: Richard <legalize at xmission.com>
Date: Thu, 20 Jan 2022 01:19:19 -0700
Subject: [PATCH] [clang-tidy] Add bugprone-macro-condition check

Warns about inconsistent macro usage in preprocessor conditions.

  #define USE_FOO 0
  #ifdef USE_FOO
  // ...no preprocessor directives testing the value of USE_FOO
  #endif

Here USE_FOO is defined to a value (and furthermore defined to
evaluate to false) but the preprocessor condition only checks for
the macro being defined.

Fixes #27438
---
 .../bugprone/BugproneTidyModule.cpp           |   3 +
 .../clang-tidy/bugprone/CMakeLists.txt        |   1 +
 .../bugprone/MacroConditionCheck.cpp          | 383 ++++++++++++++++++
 .../clang-tidy/bugprone/MacroConditionCheck.h |  30 ++
 clang-tools-extra/docs/ReleaseNotes.md        |   5 +
 .../checks/bugprone/macro-condition.md        |  51 +++
 .../docs/clang-tidy/checks/list.md            |   1 +
 .../bugprone/macro-condition-command-line.cpp |  20 +
 .../checkers/bugprone/macro-condition.cpp     | 126 ++++++
 9 files changed, 620 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/macro-condition.md
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-command-line.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition.cpp

diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 3aa39d10ceb5d..e39d90a1177da 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -46,6 +46,7 @@
 #include "IntegerDivisionCheck.h"
 #include "InvalidEnumDefaultInitializationCheck.h"
 #include "LambdaFunctionNameCheck.h"
+#include "MacroConditionCheck.h"
 #include "MacroParenthesesCheck.h"
 #include "MacroRepeatedSideEffectsCheck.h"
 #include "MisleadingSetterOfReferenceCheck.h"
@@ -203,6 +204,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-invalid-enum-default-initialization");
     CheckFactories.registerCheck<LambdaFunctionNameCheck>(
         "bugprone-lambda-function-name");
+    CheckFactories.registerCheck<MacroConditionCheck>(
+        "bugprone-macro-condition");
     CheckFactories.registerCheck<MacroParenthesesCheck>(
         "bugprone-macro-parentheses");
     CheckFactories.registerCheck<MacroRepeatedSideEffectsCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 43e85b1407f21..7c023e7458cfd 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -50,6 +50,7 @@ add_clang_library(clangTidyBugproneModule STATIC
   InfiniteLoopCheck.cpp
   IntegerDivisionCheck.cpp
   LambdaFunctionNameCheck.cpp
+  MacroConditionCheck.cpp
   MacroParenthesesCheck.cpp
   MacroRepeatedSideEffectsCheck.cpp
   MisleadingSetterOfReferenceCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.cpp
new file mode 100644
index 0000000000000..8701ffaebbc19
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.cpp
@@ -0,0 +1,383 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "MacroConditionCheck.h"
+#include "clang/Basic/DiagnosticIDs.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Lex/MacroInfo.h"
+#include "clang/Lex/PPCallbacks.h"
+#include "clang/Lex/Preprocessor.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringSet.h"
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace clang::tidy::bugprone {
+
+namespace {
+class MacroConditionCallbacks : public PPCallbacks {
+public:
+  MacroConditionCallbacks(MacroConditionCheck *Check, const SourceManager &SM,
+                          Preprocessor &PP)
+      : Check(Check), SM(SM), PP(PP) {}
+
+  void If(SourceLocation Loc, SourceRange ConditionRange,
+          ConditionValueKind ConditionValue) override;
+  void Ifdef(SourceLocation Loc, const Token &MacroNameTok,
+             const MacroDefinition &MD) override;
+  void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
+              const MacroDefinition &MD) override;
+  void Elif(SourceLocation Loc, SourceRange ConditionRange,
+            ConditionValueKind ConditionValue, SourceLocation IfLoc) override;
+  void Elifdef(SourceLocation Loc, const Token &MacroNameTok,
+               const MacroDefinition &MD) override;
+  void Elifdef(SourceLocation Loc, SourceRange ConditionRange,
+               SourceLocation IfLoc) override;
+  void Elifndef(SourceLocation Loc, const Token &MacroNameTok,
+                const MacroDefinition &MD) override;
+  void Elifndef(SourceLocation Loc, SourceRange ConditionRange,
+                SourceLocation IfLoc) override;
+  void Else(SourceLocation Loc, SourceLocation IfLoc) override;
+  void Endif(SourceLocation Loc, SourceLocation IfLoc) override;
+  void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,
+                      const MacroDirective *Undef) override;
+
+private:
+  struct MacroReference {
+    std::string Name;
+    SourceLocation Loc;
+  };
+
+  struct ConditionReferences {
+    SmallVector<MacroReference, 2> Definition;
+    SmallVector<MacroReference, 2> Value;
+  };
+
+  struct DefinitionCheck {
+    std::string Name;
+    SourceLocation DefinitionLoc;
+    SourceLocation CheckLoc;
+    bool ValueTested = false;
+  };
+
+  struct ConditionalBranch {
+    SmallVector<DefinitionCheck, 2> Checks;
+    llvm::StringSet<> DefinitionTests;
+  };
+
+  ConditionReferences referencesInCondition(SourceRange ConditionRange) const;
+  MacroReference referenceFromRange(SourceRange Range,
+                                    SourceLocation Loc) const;
+  bool isIgnoredIdentifier(StringRef Name) const;
+  void startCondition(const ConditionReferences &References);
+  void startDefinitionCondition(StringRef Name, SourceLocation Loc);
+  void nextBranch(const ConditionReferences &References = {});
+  void processReferences(const ConditionReferences &References);
+  void finishBranch(ConditionalBranch &Branch);
+  void checkDefinitionReference(const MacroReference &Reference,
+                                ConditionalBranch &Branch);
+  void checkValueReference(const MacroReference &Reference);
+  bool isDefinitionTestActive(StringRef Name) const;
+
+  SmallVector<ConditionalBranch, 8> Conditions;
+  MacroConditionCheck *Check;
+  const SourceManager &SM;
+  Preprocessor &PP;
+};
+
+} // namespace
+
+static StringRef getTokenName(const Token &Tok) {
+  if (Tok.is(tok::raw_identifier))
+    return Tok.getRawIdentifier();
+  if (const IdentifierInfo *Info = Tok.getIdentifierInfo())
+    return Info->getName();
+  return {};
+}
+
+static bool skipFunctionLikeInvocation(ArrayRef<Token> Tokens, size_t &Index) {
+  if (Index + 1 >= Tokens.size() || Tokens[Index + 1].isNot(tok::l_paren))
+    return false;
+
+  unsigned ParenthesisDepth = 0;
+  do {
+    ++Index;
+    if (Tokens[Index].is(tok::l_paren))
+      ++ParenthesisDepth;
+    else if (Tokens[Index].is(tok::r_paren))
+      --ParenthesisDepth;
+  } while (Index + 1 < Tokens.size() && ParenthesisDepth != 0);
+  return true;
+}
+
+MacroConditionCallbacks::ConditionReferences
+MacroConditionCallbacks::referencesInCondition(
+    SourceRange ConditionRange) const {
+  ConditionReferences References;
+  const SourceLocation BeginLoc = SM.getExpansionLoc(ConditionRange.getBegin());
+  if (BeginLoc.isInvalid())
+    return References;
+
+  const std::pair<FileID, unsigned> Decomposed = SM.getDecomposedLoc(BeginLoc);
+  bool Invalid = false;
+  StringRef Buffer = SM.getBufferData(Decomposed.first, &Invalid);
+  if (Invalid || Decomposed.second >= Buffer.size())
+    return References;
+
+  size_t End = Decomposed.second;
+  while (End < Buffer.size()) {
+    if (Buffer[End] != '\r' && Buffer[End] != '\n') {
+      ++End;
+      continue;
+    }
+
+    const size_t Newline = End;
+    if (Newline > Decomposed.second && Buffer[Newline - 1] == '\\') {
+      if (Buffer[End] == '\r' && End + 1 < Buffer.size() &&
+          Buffer[End + 1] == '\n')
+        ++End;
+      ++End;
+      continue;
+    }
+    break;
+  }
+
+  std::string Text = Buffer.slice(Decomposed.second, End).str();
+  Lexer Lex(BeginLoc, PP.getLangOpts(), Text.data(), Text.data(),
+            Text.data() + Text.size());
+  SmallVector<Token, 16> Tokens;
+  Token Tok;
+  bool AtEnd = false;
+  do {
+    AtEnd = Lex.LexFromRawLexer(Tok);
+    if (Tok.isNot(tok::eof))
+      Tokens.push_back(Tok);
+  } while (!AtEnd);
+
+  for (size_t Index = 0; Index < Tokens.size(); ++Index) {
+    const Token &Current = Tokens[Index];
+    if (!Current.is(tok::raw_identifier))
+      continue;
+
+    StringRef Name = Current.getRawIdentifier();
+    if (Name != "defined") {
+      if (skipFunctionLikeInvocation(Tokens, Index))
+        continue;
+      if (!isIgnoredIdentifier(Name))
+        References.Value.push_back({Name.str(), Current.getLocation()});
+      continue;
+    }
+
+    const SourceLocation DefinedLoc = Current.getLocation();
+    ++Index;
+    if (Index < Tokens.size() && Tokens[Index].is(tok::l_paren))
+      ++Index;
+    if (Index < Tokens.size() && Tokens[Index].is(tok::raw_identifier))
+      References.Definition.push_back(
+          {Tokens[Index].getRawIdentifier().str(), DefinedLoc});
+  }
+  return References;
+}
+
+MacroConditionCallbacks::MacroReference
+MacroConditionCallbacks::referenceFromRange(SourceRange Range,
+                                            SourceLocation Loc) const {
+  ConditionReferences References = referencesInCondition(Range);
+  if (!References.Value.empty()) {
+    References.Value.front().Loc = Loc;
+    return std::move(References.Value.front());
+  }
+  return {{}, Loc};
+}
+
+bool MacroConditionCallbacks::isIgnoredIdentifier(StringRef Name) const {
+  const IdentifierInfo *Info = PP.getIdentifierInfo(Name);
+  return Name == "true" || Name == "false" ||
+         Info->isCPlusPlusOperatorKeyword();
+}
+
+void MacroConditionCallbacks::startCondition(
+    const ConditionReferences &References) {
+  Conditions.emplace_back();
+  processReferences(References);
+}
+
+void MacroConditionCallbacks::startDefinitionCondition(StringRef Name,
+                                                       SourceLocation Loc) {
+  ConditionReferences References;
+  if (!Name.empty())
+    References.Definition.push_back({Name.str(), Loc});
+  startCondition(References);
+}
+
+void MacroConditionCallbacks::nextBranch(
+    const ConditionReferences &References) {
+  if (Conditions.empty())
+    return;
+  finishBranch(Conditions.back());
+  Conditions.back().Checks.clear();
+  Conditions.back().DefinitionTests.clear();
+  processReferences(References);
+}
+
+void MacroConditionCallbacks::processReferences(
+    const ConditionReferences &References) {
+  if (Conditions.empty())
+    return;
+
+  ConditionalBranch &Branch = Conditions.back();
+  for (const MacroReference &Reference : References.Definition)
+    checkDefinitionReference(Reference, Branch);
+  for (const MacroReference &Reference : References.Value)
+    checkValueReference(Reference);
+}
+
+void MacroConditionCallbacks::checkDefinitionReference(
+    const MacroReference &Reference, ConditionalBranch &Branch) {
+  Branch.DefinitionTests.insert(Reference.Name);
+
+  const IdentifierInfo *Info = PP.getIdentifierInfo(Reference.Name);
+  const MacroDefinition Definition = PP.getMacroDefinition(Info);
+  const MacroInfo *Macro = Definition.getMacroInfo();
+  if (!Macro || Macro->isBuiltinMacro() || Macro->isFunctionLike() ||
+      Macro->tokens().empty())
+    return;
+
+  const SourceLocation DefinitionLoc = Macro->getDefinitionLoc();
+  Branch.Checks.push_back(
+      {Reference.Name, DefinitionLoc, Reference.Loc, false});
+}
+
+void MacroConditionCallbacks::checkValueReference(
+    const MacroReference &Reference) {
+  const IdentifierInfo *Info = PP.getIdentifierInfo(Reference.Name);
+  const MacroInfo *Macro = PP.getMacroDefinition(Info).getMacroInfo();
+  if (!Macro && !isDefinitionTestActive(Reference.Name))
+    Check->diag(Reference.Loc, "Undefined macro '%0' checked here for value")
+        << Reference.Name;
+
+  for (ConditionalBranch &Branch : Conditions)
+    for (DefinitionCheck &Definition : Branch.Checks)
+      if (Macro && Definition.Name == Reference.Name &&
+          Macro->getDefinitionLoc() == Definition.DefinitionLoc)
+        Definition.ValueTested = true;
+}
+
+bool MacroConditionCallbacks::isDefinitionTestActive(StringRef Name) const {
+  return llvm::any_of(Conditions, [Name](const ConditionalBranch &Branch) {
+    return Branch.DefinitionTests.contains(Name);
+  });
+}
+
+void MacroConditionCallbacks::finishBranch(ConditionalBranch &Branch) {
+  for (const DefinitionCheck &Definition : Branch.Checks) {
+    if (Definition.ValueTested)
+      continue;
+
+    Check->diag(Definition.CheckLoc,
+                "Macro '%0' defined with a value and checked here for "
+                "definition")
+        << Definition.Name;
+    if (Definition.DefinitionLoc.isValid() &&
+        !SM.getFilename(Definition.DefinitionLoc).empty())
+      Check->diag(Definition.DefinitionLoc, "Macro '%0' defined here",
+                  DiagnosticIDs::Note)
+          << Definition.Name;
+  }
+}
+
+void MacroConditionCallbacks::If(SourceLocation Loc, SourceRange ConditionRange,
+                                 ConditionValueKind ConditionValue) {
+  startCondition(referencesInCondition(ConditionRange));
+}
+
+void MacroConditionCallbacks::Ifdef(SourceLocation Loc,
+                                    const Token &MacroNameTok,
+                                    const MacroDefinition &MD) {
+  startDefinitionCondition(getTokenName(MacroNameTok), Loc);
+}
+
+void MacroConditionCallbacks::Ifndef(SourceLocation Loc,
+                                     const Token &MacroNameTok,
+                                     const MacroDefinition &MD) {
+  startDefinitionCondition(getTokenName(MacroNameTok), Loc);
+}
+
+void MacroConditionCallbacks::Elif(SourceLocation Loc,
+                                   SourceRange ConditionRange,
+                                   ConditionValueKind ConditionValue,
+                                   SourceLocation IfLoc) {
+  nextBranch(referencesInCondition(ConditionRange));
+}
+
+void MacroConditionCallbacks::Elifdef(SourceLocation Loc,
+                                      const Token &MacroNameTok,
+                                      const MacroDefinition &MD) {
+  ConditionReferences References;
+  References.Definition.push_back({getTokenName(MacroNameTok).str(), Loc});
+  nextBranch(References);
+}
+
+void MacroConditionCallbacks::Elifdef(SourceLocation Loc,
+                                      SourceRange ConditionRange,
+                                      SourceLocation IfLoc) {
+  MacroReference Reference = referenceFromRange(ConditionRange, Loc);
+  ConditionReferences References;
+  if (!Reference.Name.empty())
+    References.Definition.push_back(std::move(Reference));
+  nextBranch(References);
+}
+
+void MacroConditionCallbacks::Elifndef(SourceLocation Loc,
+                                       const Token &MacroNameTok,
+                                       const MacroDefinition &MD) {
+  ConditionReferences References;
+  References.Definition.push_back({getTokenName(MacroNameTok).str(), Loc});
+  nextBranch(References);
+}
+
+void MacroConditionCallbacks::Elifndef(SourceLocation Loc,
+                                       SourceRange ConditionRange,
+                                       SourceLocation IfLoc) {
+  MacroReference Reference = referenceFromRange(ConditionRange, Loc);
+  ConditionReferences References;
+  if (!Reference.Name.empty())
+    References.Definition.push_back(std::move(Reference));
+  nextBranch(References);
+}
+
+void MacroConditionCallbacks::Else(SourceLocation Loc, SourceLocation IfLoc) {
+  nextBranch();
+}
+
+void MacroConditionCallbacks::Endif(SourceLocation Loc, SourceLocation IfLoc) {
+  if (Conditions.empty())
+    return;
+  finishBranch(Conditions.back());
+  Conditions.pop_back();
+}
+
+void MacroConditionCallbacks::MacroUndefined(const Token &MacroNameTok,
+                                             const MacroDefinition &MD,
+                                             const MacroDirective *Undef) {
+  if (!Undef)
+    return;
+  const StringRef Name = getTokenName(MacroNameTok);
+  for (ConditionalBranch &Branch : Conditions)
+    Branch.DefinitionTests.erase(Name);
+}
+
+void MacroConditionCheck::registerPPCallbacks(const SourceManager &SM,
+                                              Preprocessor *PP,
+                                              Preprocessor *ModuleExpanderPP) {
+  PP->addPPCallbacks(std::make_unique<MacroConditionCallbacks>(this, SM, *PP));
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.h b/clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.h
new file mode 100644
index 0000000000000..ab4fb90490268
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.h
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_MACROCONDITIONCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROCONDITIONCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Warns about inconsistent macro usage in preprocessor conditions.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone-macro-condition.html
+class MacroConditionCheck : public ClangTidyCheck {
+public:
+  using ClangTidyCheck::ClangTidyCheck;
+
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROCONDITIONCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index c2ee1b06c43e0..042986304f503 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -129,6 +129,11 @@ infrastructure are described first, followed by tool-specific sections.
 
 #### New checks
 
+- New {doc}`bugprone-macro-condition
+  <clang-tidy/checks/bugprone/macro-condition>` check.
+
+  Warns about inconsistent macro usage in preprocessor conditions.
+
 - New {doc}`llvm-invalid-regex-pattern
   <clang-tidy/checks/llvm/invalid-regex-pattern>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/macro-condition.md b/clang-tools-extra/docs/clang-tidy/checks/bugprone/macro-condition.md
new file mode 100644
index 0000000000000..e880c2b8c8e1e
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/macro-condition.md
@@ -0,0 +1,51 @@
+```{title} clang-tidy - bugprone-macro-condition
+```
+
+# bugprone-macro-condition
+
+Warns about inconsistent macro usage in preprocessor conditions.
+
+Given the following code:
+
+```c++
+#define USE_FOO 0
+// ...
+#if defined(USE_FOO)
+  // ...
+#endif
+```
+
+Here `USE_FOO` is defined to a value that would evaluate to false in a
+preprocessor condition, but checked for definition and not for its value.
+Was the intention to evaluate `USE_FOO` for a `true` expression, or was
+the intention to merely check whether or not the macro was defined?
+
+If the code later contains:
+
+```c++
+#if USE_FOO
+  // ...
+#endif
+```
+
+Then the suspicions are raised further.
+
+The following scenarios result in warnings; no fixes
+are offered as the scenarios are all ambiguous.
+
+- A macro is defined to a value,
+  but it is checked for definition in a condition.
+- A macro in a condition is checked for definition in one location
+  and for value in another location.
+
+To resolve a warning, decide which property of the macro is important:
+
+- If the macro's value is important, keep the value in its definition and
+  refactor definition tests to test the value, for example with
+  ``#if USE_FOO`` or an explicit comparison.
+- If the macro's presence or absence is important, make it a presence-only
+  macro and refactor value tests to use ``defined(USE_FOO)`` or
+  ``!defined(USE_FOO)`` consistently.
+
+Function-like macros are ignored, including identifiers passed in their
+argument lists.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.md b/clang-tools-extra/docs/clang-tidy/checks/list.md
index a74a26e691053..8fd3d7408c9f5 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.md
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.md
@@ -118,6 +118,7 @@ readability/*
 | {doc}`bugprone-integer-division <bugprone/integer-division>` |  |
 | {doc}`bugprone-invalid-enum-default-initialization <bugprone/invalid-enum-default-initialization>` |  |
 | {doc}`bugprone-lambda-function-name <bugprone/lambda-function-name>` |  |
+| {doc}`bugprone-macro-condition <bugprone/macro-condition>` |  |
 | {doc}`bugprone-macro-parentheses <bugprone/macro-parentheses>` | Yes |
 | {doc}`bugprone-macro-repeated-side-effects <bugprone/macro-repeated-side-effects>` |  |
 | {doc}`bugprone-misleading-setter-of-reference <bugprone/misleading-setter-of-reference>` |  |
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-command-line.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-command-line.cpp
new file mode 100644
index 0000000000000..626b27f44a2ad
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-command-line.cpp
@@ -0,0 +1,20 @@
+// RUN: %check_clang_tidy -check-suffix=DEFINED %s \
+// RUN:   bugprone-macro-condition %t -- -- -DCOMMAND_LINE_MACRO=0
+// RUN: %check_clang_tidy -check-suffix=UNDEFINED %s \
+// RUN:   bugprone-macro-condition %t -- -- -UCOMMAND_LINE_MACRO
+
+// With -UCOMMAND_LINE_MACRO, this condition is equivalent to:
+//
+// #undef COMMAND_LINE_MACRO
+// #if COMMAND_LINE_MACRO
+#if COMMAND_LINE_MACRO
+// CHECK-MESSAGES-UNDEFINED: :[[@LINE-1]]:5: warning: Undefined macro 'COMMAND_LINE_MACRO' checked here for value
+#endif
+
+// With -DCOMMAND_LINE_MACRO=0, this condition is equivalent to:
+//
+// #define COMMAND_LINE_MACRO 0
+// #ifdef COMMAND_LINE_MACRO
+#ifdef COMMAND_LINE_MACRO
+// CHECK-MESSAGES-DEFINED: :[[@LINE-1]]:2: warning: Macro 'COMMAND_LINE_MACRO' defined with a value and checked here for definition
+#endif
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition.cpp
new file mode 100644
index 0000000000000..79f8d5b48a349
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition.cpp
@@ -0,0 +1,126 @@
+// RUN: %check_clang_tidy %s bugprone-macro-condition %t
+
+#define USE_FOO 0
+
+#if defined(USE_FOO)
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Macro 'USE_FOO' defined with a value and checked here for definition
+// CHECK-MESSAGES: :[[#USE_FOO_DEFINITION:]]:9: note: Macro 'USE_FOO' defined here
+void f()
+{
+  extern void foo();
+  foo();
+}
+#endif
+
+#if 0
+#elif OTHER_MACRO
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: Undefined macro 'OTHER_MACRO' checked here for value
+#elifdef OTHER_MACRO2
+#else
+#endif
+
+#if !defined(USE_FOO)
+// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: Macro 'USE_FOO' defined with a value and checked here for definition
+// CHECK-MESSAGES: :[[#USE_FOO_DEFINITION]]:9: note: Macro 'USE_FOO' defined here
+void f2()
+{
+  extern void notFoo();
+  notFoo();
+}
+#endif
+
+#ifdef USE_FOO
+// CHECK-MESSAGES: :[[@LINE-1]]:2: warning: Macro 'USE_FOO' defined with a value and checked here for definition
+// CHECK-MESSAGES: :[[#USE_FOO_DEFINITION]]:9: note: Macro 'USE_FOO' defined here
+void f3()
+{
+  extern void foo();
+  foo();
+}
+#endif
+
+#ifndef USE_FOO
+// CHECK-MESSAGES: :[[@LINE-1]]:2: warning: Macro 'USE_FOO' defined with a value and checked here for definition
+// CHECK-MESSAGES: :[[#USE_FOO_DEFINITION]]:9: note: Macro 'USE_FOO' defined here
+void f4()
+{
+  extern void notFoo();
+  notFoo();
+}
+#endif
+
+#if 0
+#elif defined(USE_FOO)
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: Macro 'USE_FOO' defined with a value and checked here for definition
+// CHECK-MESSAGES: :[[#USE_FOO_DEFINITION]]:9: note: Macro 'USE_FOO' defined here
+void f5()
+{
+  extern void foo();
+  foo();
+}
+#endif
+
+#define USE_GRONK 0
+#ifdef USE_GRONK
+#if USE_GRONK
+void f6()
+{
+  extern void foo();
+  foo();
+}
+#endif
+#endif
+
+#if 0
+#elif defined(USE_GRONK)
+#if USE_GRONK
+void f7()
+{
+  extern void foo();
+  foo();
+}
+#endif
+#endif
+
+#if defined(USE_GRONK) && USE_GRONK
+void f8()
+{
+  extern void foo();
+  foo();
+}
+#endif
+
+#if __has_include(<sys/file.h>)
+#include <sys/file.h>
+#endif
+// CHECK-MESSAGES-NOT: warning: Undefined macro 'sys' checked here for value
+// CHECK-MESSAGES-NOT: warning: Undefined macro 'file' checked here for value
+// CHECK-MESSAGES-NOT: warning: Undefined macro 'h' checked here for value
+
+#if __has_builtin(__builtin_trap)
+#endif
+// CHECK-MESSAGES-NOT: warning: Undefined macro '__builtin_trap' checked here for value
+
+#if __has_cpp_attribute(gnu::always_inline)
+#endif
+// CHECK-MESSAGES-NOT: warning: Undefined macro 'gnu' checked here for value
+// CHECK-MESSAGES-NOT: warning: Undefined macro 'always_inline' checked here for value
+
+#define ALWAYS_TRUE(x) 1
+#if ALWAYS_TRUE(not_a_macro)
+#endif
+// CHECK-MESSAGES-NOT: warning: Undefined macro 'not_a_macro' checked here for value
+
+#ifdef ALWAYS_TRUE
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'ALWAYS_TRUE' defined here with a value and checked for definition
+
+#define GUARDED 1
+#if defined(GUARDED)
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Macro 'GUARDED' defined with a value and checked here for definition
+// CHECK-MESSAGES: :[[#GUARDED_DEFINITION:]]:9: note: Macro 'GUARDED' defined here
+#undef GUARDED
+#if GUARDED
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Undefined macro 'GUARDED' checked here for value
+#endif
+#endif



More information about the cfe-commits mailing list