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

>From de4e4daa7d83fc9b974be4631c64bfc79cdc190e Mon Sep 17 00:00:00 2001
From: Richard <[email protected]>
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          | 617 ++++++++++++++++++
 .../clang-tidy/bugprone/MacroConditionCheck.h |  30 +
 .../clang-tidy/modernize/MacroToEnumCheck.cpp |  22 +-
 .../AvoidUnconditionalPreprocessorIfCheck.cpp |  22 +-
 .../UseConcisePreprocessorDirectivesCheck.cpp |  55 +-
 .../clang-tidy/utils/LexerUtils.cpp           |  40 ++
 .../clang-tidy/utils/LexerUtils.h             |   8 +
 clang-tools-extra/docs/ReleaseNotes.md        |   5 +
 .../checks/bugprone/macro-condition.md        | 100 +++
 .../docs/clang-tidy/checks/list.md            |   1 +
 .../Inputs/macro-condition-cross-file.h       |   3 +
 .../bugprone/macro-condition-command-line.cpp |  19 +
 .../bugprone/macro-condition-cross-file.cpp   |   8 +
 .../checkers/bugprone/macro-condition.cpp     | 213 ++++++
 .../unittests/clang-tidy/LexerUtilsTest.cpp   |  24 +
 17 files changed, 1110 insertions(+), 61 deletions(-)
 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/Inputs/macro-condition-cross-file.h
 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-cross-file.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 3aa39d10ceb5dc..e39d90a1177dac 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 43e85b1407f21a..7c023e7458cfd0 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 00000000000000..bf5977ca15cc3e
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/MacroConditionCheck.cpp
@@ -0,0 +1,617 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "../utils/LexerUtils.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/DenseMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include <memory>
+#include <optional>
+#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 SourceRangeSkipped(SourceRange Range, SourceLocation EndifLoc) override;
+
+private:
+  enum class ReferenceKind { Definition, NegatedDefinition, Value };
+
+  struct MacroReference {
+    std::string Name;
+    SourceLocation Loc;
+    ReferenceKind Kind;
+  };
+
+  struct MacroUsage {
+    SourceLocation DefinitionTestLoc;
+    SourceLocation ValueTestLoc;
+    bool Diagnosed = false;
+  };
+
+  struct DefinitionGuard {
+    std::string Name;
+    FileID File;
+  };
+
+  using ConditionReferences = SmallVector<MacroReference, 4>;
+  using DefinitionGuards = SmallVector<DefinitionGuard, 2>;
+
+  ConditionReferences referencesInCondition(SourceRange ConditionRange) const;
+  DefinitionGuards
+  definitionGuardsInCondition(SourceRange ConditionRange) const;
+  std::optional<std::string>
+  defaultedMacroInSkippedRange(SourceRange Range) const;
+  MacroReference referenceFromRange(SourceRange Range,
+                                    SourceLocation Loc) const;
+  bool branchUnconditionallyErrors(SourceLocation Loc) const;
+  bool isEnclosedByDefinitionGuard(const MacroReference &Reference) const;
+  void beginConditional(DefinitionGuards Guards);
+  void changeConditional(DefinitionGuards Guards);
+  bool isIgnoredIdentifier(StringRef Name) const;
+  void processReferences(const ConditionReferences &References);
+  void processDefinitionReference(StringRef Name, SourceLocation Loc);
+  void checkReference(const MacroReference &Reference);
+
+  using FileUsages = llvm::DenseMap<FileID, MacroUsage>;
+  llvm::DenseMap<const MacroInfo *, FileUsages> MacroUsages;
+  SmallVector<DefinitionGuards, 8> ConditionalGuards;
+  MacroConditionCheck *Check;
+  const SourceManager &SM;
+  Preprocessor &PP;
+};
+
+} // namespace
+
+using utils::lexer::getTokenName;
+
+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;
+}
+
+static bool isNegatedDefined(ArrayRef<Token> Tokens, size_t Index) {
+  unsigned Negations = 0;
+  while (Index > 0) {
+    while (Index > 0 && Tokens[Index - 1].is(tok::l_paren))
+      --Index;
+    if (Index == 0 || Tokens[Index - 1].isNot(tok::exclaim))
+      break;
+    --Index;
+    ++Negations;
+  }
+  return Negations % 2 != 0;
+}
+
+static bool isStandardPredefinedMacro(StringRef Name) {
+  return Name == "__cplusplus" || Name == "__DATE__" || Name == "__FILE__" ||
+         Name == "__LINE__" || Name == "__TIME__" ||
+         Name.starts_with("__cpp_") || Name.starts_with("__STDC_") ||
+         Name.starts_with("__STDCPP_");
+}
+
+MacroConditionCallbacks::ConditionReferences
+MacroConditionCallbacks::referencesInCondition(
+    SourceRange ConditionRange) const {
+  ConditionReferences References;
+  const std::vector<Token> Tokens = utils::lexer::getRawTokens(
+      CharSourceRange::getTokenRange(ConditionRange), SM, PP.getLangOpts());
+
+  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.push_back(
+            {Name.str(), Current.getLocation(), ReferenceKind::Value});
+      continue;
+    }
+
+    const SourceLocation DefinedLoc = Current.getLocation();
+    const bool IsNegated = isNegatedDefined(Tokens, Index);
+    ++Index;
+    if (Index < Tokens.size() && Tokens[Index].is(tok::l_paren))
+      ++Index;
+    if (Index < Tokens.size() && Tokens[Index].is(tok::raw_identifier))
+      References.push_back({Tokens[Index].getRawIdentifier().str(), DefinedLoc,
+                            IsNegated ? ReferenceKind::NegatedDefinition
+                                      : ReferenceKind::Definition});
+  }
+  return References;
+}
+
+static bool
+parseDefinitionGuardExpression(ArrayRef<Token> Tokens, size_t &Index,
+                               SmallVectorImpl<std::string> &Guards);
+
+static bool parseDefinitionGuardPrimary(ArrayRef<Token> Tokens, size_t &Index,
+                                        SmallVectorImpl<std::string> &Guards) {
+  if (Index >= Tokens.size())
+    return false;
+
+  if (Tokens[Index].is(tok::l_paren)) {
+    ++Index;
+    if (!parseDefinitionGuardExpression(Tokens, Index, Guards) ||
+        Index >= Tokens.size() || Tokens[Index].isNot(tok::r_paren))
+      return false;
+    ++Index;
+    return true;
+  }
+
+  if (getTokenName(Tokens[Index]) != "defined")
+    return false;
+  ++Index;
+
+  const bool Parenthesized =
+      Index < Tokens.size() && Tokens[Index].is(tok::l_paren);
+  if (Parenthesized)
+    ++Index;
+  if (Index >= Tokens.size() || Tokens[Index].isNot(tok::raw_identifier))
+    return false;
+  Guards.push_back(Tokens[Index++].getRawIdentifier().str());
+  if (Parenthesized) {
+    if (Index >= Tokens.size() || Tokens[Index].isNot(tok::r_paren))
+      return false;
+    ++Index;
+  }
+  return true;
+}
+
+static bool
+parseDefinitionGuardExpression(ArrayRef<Token> Tokens, size_t &Index,
+                               SmallVectorImpl<std::string> &Guards) {
+  if (!parseDefinitionGuardPrimary(Tokens, Index, Guards))
+    return false;
+  while (Index < Tokens.size() && Tokens[Index].is(tok::ampamp)) {
+    ++Index;
+    if (!parseDefinitionGuardPrimary(Tokens, Index, Guards))
+      return false;
+  }
+  return true;
+}
+
+MacroConditionCallbacks::DefinitionGuards
+MacroConditionCallbacks::definitionGuardsInCondition(
+    SourceRange ConditionRange) const {
+  const std::vector<Token> Tokens = utils::lexer::getRawTokens(
+      CharSourceRange::getTokenRange(ConditionRange), SM, PP.getLangOpts());
+  SmallVector<std::string, 2> Names;
+  size_t Index = 0;
+  if (!parseDefinitionGuardExpression(Tokens, Index, Names) ||
+      Index != Tokens.size())
+    return {};
+
+  DefinitionGuards Guards;
+  const FileID File =
+      SM.getFileID(SM.getSpellingLoc(ConditionRange.getBegin()));
+  for (std::string &Name : Names)
+    Guards.push_back({std::move(Name), File});
+  return Guards;
+}
+
+bool MacroConditionCallbacks::branchUnconditionallyErrors(
+    SourceLocation Loc) const {
+  const SourceLocation BeginLoc = SM.getExpansionLoc(Loc);
+  if (BeginLoc.isInvalid())
+    return false;
+
+  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 false;
+
+  size_t Body = Decomposed.second;
+  while (Body < Buffer.size()) {
+    if (Buffer[Body] != '\r' && Buffer[Body] != '\n') {
+      ++Body;
+      continue;
+    }
+    if (Body > Decomposed.second && Buffer[Body - 1] == '\\') {
+      if (Buffer[Body] == '\r' && Body + 1 < Buffer.size() &&
+          Buffer[Body + 1] == '\n')
+        ++Body;
+      ++Body;
+      continue;
+    }
+    break;
+  }
+  while (Body < Buffer.size() && (Buffer[Body] == '\r' || Buffer[Body] == 
'\n'))
+    ++Body;
+
+  const SourceLocation BodyLoc =
+      SM.getLocForStartOfFile(Decomposed.first).getLocWithOffset(Body);
+  std::string Text = Buffer.drop_front(Body).str();
+  Lexer Lex(BodyLoc, PP.getLangOpts(), Text.data(), Text.data(),
+            Text.data() + Text.size());
+  unsigned Depth = 1;
+  Token Tok;
+  while (!Lex.LexFromRawLexer(Tok)) {
+    if (Tok.isNot(tok::hash) || !Tok.isAtStartOfLine())
+      continue;
+
+    Token DirectiveTok;
+    if (Lex.LexFromRawLexer(DirectiveTok))
+      return false;
+    StringRef Directive = getTokenName(DirectiveTok);
+    if (Directive == "if" || Directive == "ifdef" || Directive == "ifndef") {
+      ++Depth;
+      continue;
+    }
+    if (Directive == "endif") {
+      if (--Depth == 0)
+        return false;
+      continue;
+    }
+    if (Depth != 1)
+      continue;
+    if (Directive == "else" || Directive.starts_with("elif"))
+      return false;
+    if (Directive == "error")
+      return true;
+  }
+  return false;
+}
+
+std::optional<std::string>
+MacroConditionCallbacks::defaultedMacroInSkippedRange(SourceRange Range) const 
{
+  const SourceLocation BeginLoc = SM.getExpansionLoc(Range.getBegin());
+  const SourceLocation EndLoc = SM.getExpansionLoc(Range.getEnd());
+  if (BeginLoc.isInvalid() || EndLoc.isInvalid())
+    return std::nullopt;
+
+  const std::pair<FileID, unsigned> Begin = SM.getDecomposedLoc(BeginLoc);
+  const std::pair<FileID, unsigned> End = SM.getDecomposedLoc(EndLoc);
+  if (Begin.first != End.first || Begin.second >= End.second)
+    return std::nullopt;
+
+  bool Invalid = false;
+  StringRef Buffer = SM.getBufferData(Begin.first, &Invalid);
+  if (Invalid || End.second > Buffer.size())
+    return std::nullopt;
+
+  std::string Text = Buffer.slice(Begin.second, End.second).str();
+  Lexer Lex(BeginLoc, PP.getLangOpts(), Text.data(), Text.data(),
+            Text.data() + Text.size());
+  SmallVector<Token, 32> Tokens;
+  Token Tok;
+  bool AtEnd = false;
+  do {
+    AtEnd = Lex.LexFromRawLexer(Tok);
+    if (Tok.isNot(tok::eof))
+      Tokens.push_back(Tok);
+  } while (!AtEnd);
+
+  if (Tokens.size() < 3 || Tokens[0].isNot(tok::hash) ||
+      !Tokens[0].isAtStartOfLine())
+    return std::nullopt;
+
+  size_t Index = 2;
+  std::string Name;
+  StringRef Directive = getTokenName(Tokens[1]);
+  if (Directive == "ifndef") {
+    if (!Tokens[Index].is(tok::raw_identifier))
+      return std::nullopt;
+    Name = Tokens[Index++].getRawIdentifier().str();
+  } else if (Directive == "if") {
+    if (Tokens[Index].isNot(tok::exclaim))
+      return std::nullopt;
+    ++Index;
+    if (Index >= Tokens.size() || getTokenName(Tokens[Index]) != "defined")
+      return std::nullopt;
+    ++Index;
+    if (Index < Tokens.size() && Tokens[Index].is(tok::l_paren))
+      ++Index;
+    if (Index >= Tokens.size() || Tokens[Index].isNot(tok::raw_identifier))
+      return std::nullopt;
+    Name = Tokens[Index++].getRawIdentifier().str();
+    if (Index < Tokens.size() && Tokens[Index].is(tok::r_paren))
+      ++Index;
+  } else {
+    return std::nullopt;
+  }
+
+  if (Index < Tokens.size() && !Tokens[Index].isAtStartOfLine())
+    return std::nullopt;
+
+  unsigned Depth = 1;
+  for (; Index + 1 < Tokens.size(); ++Index) {
+    if (Tokens[Index].isNot(tok::hash) || !Tokens[Index].isAtStartOfLine())
+      continue;
+
+    StringRef NestedDirective = getTokenName(Tokens[++Index]);
+    if (NestedDirective == "if" || NestedDirective == "ifdef" ||
+        NestedDirective == "ifndef") {
+      ++Depth;
+      continue;
+    }
+    if (NestedDirective == "endif") {
+      if (--Depth == 0)
+        break;
+      continue;
+    }
+    if (Depth != 1)
+      continue;
+    if (NestedDirective == "else" || NestedDirective.starts_with("elif"))
+      break;
+    if (NestedDirective != "define" || Index + 2 >= Tokens.size() ||
+        getTokenName(Tokens[Index + 1]) != Name ||
+        Tokens[Index + 2].isAtStartOfLine())
+      continue;
+
+    const Token &MacroName = Tokens[Index + 1];
+    const Token &Replacement = Tokens[Index + 2];
+    const SourceLocation MacroNameEnd = Lexer::getLocForEndOfToken(
+        MacroName.getLocation(), 0, SM, PP.getLangOpts());
+    if (Replacement.is(tok::l_paren) &&
+        MacroNameEnd == Replacement.getLocation())
+      continue;
+    return Name;
+  }
+  return std::nullopt;
+}
+
+MacroConditionCallbacks::MacroReference
+MacroConditionCallbacks::referenceFromRange(SourceRange Range,
+                                            SourceLocation Loc) const {
+  ConditionReferences References = referencesInCondition(Range);
+  if (!References.empty()) {
+    References.front().Loc = Loc;
+    References.front().Kind = ReferenceKind::Definition;
+    return std::move(References.front());
+  }
+  return {{}, Loc, ReferenceKind::Definition};
+}
+
+bool MacroConditionCallbacks::isIgnoredIdentifier(StringRef Name) const {
+  const IdentifierInfo *Info = PP.getIdentifierInfo(Name);
+  return Name == "true" || Name == "false" ||
+         Info->isCPlusPlusOperatorKeyword();
+}
+
+bool MacroConditionCallbacks::isEnclosedByDefinitionGuard(
+    const MacroReference &Reference) const {
+  const FileID File = SM.getFileID(SM.getSpellingLoc(Reference.Loc));
+  for (const DefinitionGuards &Guards : reverse(ConditionalGuards)) {
+    for (const DefinitionGuard &Guard : Guards)
+      if (Guard.File == File && Guard.Name == Reference.Name)
+        return true;
+  }
+  return false;
+}
+
+void MacroConditionCallbacks::beginConditional(DefinitionGuards Guards) {
+  ConditionalGuards.push_back(std::move(Guards));
+}
+
+void MacroConditionCallbacks::changeConditional(DefinitionGuards Guards) {
+  if (!ConditionalGuards.empty())
+    ConditionalGuards.back() = std::move(Guards);
+}
+
+void MacroConditionCallbacks::processReferences(
+    const ConditionReferences &References) {
+  for (const MacroReference &Reference : References) {
+    bool IsCompoundReference = false;
+    for (const MacroReference &Other : References) {
+      if (Reference.Name == Other.Name && Reference.Kind != Other.Kind) {
+        IsCompoundReference = true;
+        break;
+      }
+    }
+    if (!IsCompoundReference)
+      checkReference(Reference);
+  }
+}
+
+void MacroConditionCallbacks::processDefinitionReference(StringRef Name,
+                                                         SourceLocation Loc) {
+  if (!Name.empty())
+    checkReference({Name.str(), Loc, ReferenceKind::Definition});
+}
+
+void MacroConditionCallbacks::checkReference(const MacroReference &Reference) {
+  if (Reference.Kind == ReferenceKind::NegatedDefinition ||
+      isStandardPredefinedMacro(Reference.Name) ||
+      (Reference.Kind == ReferenceKind::Value &&
+       isEnclosedByDefinitionGuard(Reference)))
+    return;
+
+  const IdentifierInfo *Info = PP.getIdentifierInfo(Reference.Name);
+  const MacroInfo *Macro = PP.getMacroDefinition(Info).getMacroInfo();
+  if (!Macro || Macro->isBuiltinMacro() || Macro->isFunctionLike() ||
+      Macro->tokens().empty())
+    return;
+
+  const SourceLocation SpellingLoc = SM.getSpellingLoc(Reference.Loc);
+  if (SpellingLoc.isInvalid())
+    return;
+
+  MacroUsage &Usage = MacroUsages[Macro][SM.getFileID(SpellingLoc)];
+  const bool IsDefinition = Reference.Kind == ReferenceKind::Definition;
+  SourceLocation &CurrentLoc =
+      IsDefinition ? Usage.DefinitionTestLoc : Usage.ValueTestLoc;
+  const SourceLocation OtherLoc =
+      IsDefinition ? Usage.ValueTestLoc : Usage.DefinitionTestLoc;
+  if (CurrentLoc.isInvalid())
+    CurrentLoc = Reference.Loc;
+  if (Usage.Diagnosed || OtherLoc.isInvalid())
+    return;
+
+  const unsigned Kind = IsDefinition ? 0 : 1;
+  Check->diag(Reference.Loc,
+              "Macro '%0' checked here for %select{definition|value}1 after "
+              "being checked for %select{value|definition}1")
+      << Reference.Name << Kind;
+  Check->diag(OtherLoc,
+              "Macro '%0' first checked here for "
+              "%select{value|definition}1",
+              DiagnosticIDs::Note)
+      << Reference.Name << Kind;
+  Usage.Diagnosed = true;
+}
+
+void MacroConditionCallbacks::If(SourceLocation Loc, SourceRange 
ConditionRange,
+                                 ConditionValueKind ConditionValue) {
+  if (!branchUnconditionallyErrors(Loc))
+    processReferences(referencesInCondition(ConditionRange));
+  beginConditional(definitionGuardsInCondition(ConditionRange));
+}
+
+void MacroConditionCallbacks::Ifdef(SourceLocation Loc,
+                                    const Token &MacroNameTok,
+                                    const MacroDefinition &MD) {
+  const StringRef Name = getTokenName(MacroNameTok);
+  if (!branchUnconditionallyErrors(Loc))
+    processDefinitionReference(Name, Loc);
+  DefinitionGuards Guards;
+  if (!Name.empty())
+    Guards.push_back({Name.str(), SM.getFileID(SM.getSpellingLoc(
+                                      MacroNameTok.getLocation()))});
+  beginConditional(std::move(Guards));
+}
+
+void MacroConditionCallbacks::Ifndef(SourceLocation, const Token &,
+                                     const MacroDefinition &) {
+  beginConditional({});
+}
+
+void MacroConditionCallbacks::Elif(SourceLocation Loc,
+                                   SourceRange ConditionRange,
+                                   ConditionValueKind ConditionValue,
+                                   SourceLocation IfLoc) {
+  changeConditional({});
+  if (!branchUnconditionallyErrors(Loc))
+    processReferences(referencesInCondition(ConditionRange));
+  changeConditional(definitionGuardsInCondition(ConditionRange));
+}
+
+void MacroConditionCallbacks::Elifdef(SourceLocation Loc,
+                                      const Token &MacroNameTok,
+                                      const MacroDefinition &MD) {
+  changeConditional({});
+  const StringRef Name = getTokenName(MacroNameTok);
+  if (!branchUnconditionallyErrors(Loc))
+    processDefinitionReference(Name, Loc);
+  DefinitionGuards Guards;
+  if (!Name.empty())
+    Guards.push_back({Name.str(), SM.getFileID(SM.getSpellingLoc(
+                                      MacroNameTok.getLocation()))});
+  changeConditional(std::move(Guards));
+}
+
+void MacroConditionCallbacks::Elifdef(SourceLocation Loc,
+                                      SourceRange ConditionRange,
+                                      SourceLocation IfLoc) {
+  changeConditional({});
+  MacroReference Reference = referenceFromRange(ConditionRange, Loc);
+  if (!Reference.Name.empty() && !branchUnconditionallyErrors(Loc))
+    checkReference(Reference);
+  DefinitionGuards Guards;
+  if (!Reference.Name.empty())
+    Guards.push_back({std::move(Reference.Name),
+                      SM.getFileID(SM.getSpellingLoc(Reference.Loc))});
+  changeConditional(std::move(Guards));
+}
+
+void MacroConditionCallbacks::Elifndef(SourceLocation, const Token &,
+                                       const MacroDefinition &) {
+  changeConditional({});
+}
+
+void MacroConditionCallbacks::Elifndef(SourceLocation, SourceRange,
+                                       SourceLocation) {
+  changeConditional({});
+}
+
+void MacroConditionCallbacks::Else(SourceLocation, SourceLocation) {
+  changeConditional({});
+}
+
+void MacroConditionCallbacks::Endif(SourceLocation, SourceLocation) {
+  if (!ConditionalGuards.empty())
+    ConditionalGuards.pop_back();
+}
+
+void MacroConditionCallbacks::SourceRangeSkipped(SourceRange Range,
+                                                 SourceLocation EndifLoc) {
+  std::optional<std::string> Name = defaultedMacroInSkippedRange(Range);
+  if (!Name)
+    return;
+
+  const IdentifierInfo *Info = PP.getIdentifierInfo(*Name);
+  const MacroInfo *Macro = PP.getMacroDefinition(Info).getMacroInfo();
+  const auto MacroIt = MacroUsages.find(Macro);
+  if (MacroIt == MacroUsages.end())
+    return;
+
+  const SourceLocation GuardLoc = SM.getSpellingLoc(Range.getBegin());
+  const FileID GuardFile = SM.getFileID(GuardLoc);
+  const auto FileIt = MacroIt->second.find(GuardFile);
+  if (FileIt == MacroIt->second.end())
+    return;
+
+  SourceLocation &DefinitionLoc = FileIt->second.DefinitionTestLoc;
+  if (DefinitionLoc.isValid() && SM.getSpellingLineNumber(DefinitionLoc) ==
+                                     SM.getSpellingLineNumber(GuardLoc))
+    DefinitionLoc = {};
+}
+
+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 00000000000000..ab4fb90490268f
--- /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/clang-tidy/modernize/MacroToEnumCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp
index bef2e3cca57a77..2b5b8fe0e07e7b 100644
--- a/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "MacroToEnumCheck.h"
+#include "../utils/LexerUtils.h"
 #include "IntegralLiteralExpressionMatcher.h"
 
 #include "clang/AST/ASTContext.h"
@@ -19,6 +20,8 @@
 
 namespace clang::tidy::modernize {
 
+using utils::lexer::getTokenName;
+
 static bool hasOnlyComments(SourceLocation Loc, const LangOptions &Options,
                             StringRef Text) {
   // Use a lexer to look for tokens; if we find something other than a single
@@ -72,11 +75,6 @@ static bool hasOnlyComments(SourceLocation Loc, const 
LangOptions &Options,
   return true;
 }
 
-static StringRef getTokenName(const Token &Tok) {
-  return Tok.is(tok::raw_identifier) ? Tok.getRawIdentifier()
-                                     : Tok.getIdentifierInfo()->getName();
-}
-
 namespace {
 
 struct EnumMacro {
@@ -258,17 +256,9 @@ void MacroToEnumCallbacks::conditionStart(const 
SourceLocation &Loc) {
 }
 
 void MacroToEnumCallbacks::checkCondition(SourceRange Range) {
-  const CharSourceRange CharRange = Lexer::makeFileCharRange(
-      CharSourceRange::getTokenRange(Range), SM, LangOpts);
-  std::string Text = Lexer::getSourceText(CharRange, SM, LangOpts).str();
-  Lexer Lex(CharRange.getBegin(), LangOpts, Text.data(), Text.data(),
-            Text.data() + Text.size());
-  Token Tok;
-  bool End = false;
-  while (!End) {
-    End = Lex.LexFromRawLexer(Tok);
-    if (Tok.is(tok::raw_identifier) &&
-        Tok.getRawIdentifier().str() != "defined")
+  for (const Token &Tok : utils::lexer::getRawTokens(
+           CharSourceRange::getTokenRange(Range), SM, LangOpts)) {
+    if (Tok.is(tok::raw_identifier) && getTokenName(Tok) != "defined")
       checkName(Tok);
   }
 }
diff --git 
a/clang-tools-extra/clang-tidy/readability/AvoidUnconditionalPreprocessorIfCheck.cpp
 
b/clang-tools-extra/clang-tidy/readability/AvoidUnconditionalPreprocessorIfCheck.cpp
index a962c4659cfbf0..0519184fe15943 100644
--- 
a/clang-tools-extra/clang-tidy/readability/AvoidUnconditionalPreprocessorIfCheck.cpp
+++ 
b/clang-tools-extra/clang-tidy/readability/AvoidUnconditionalPreprocessorIfCheck.cpp
@@ -44,26 +44,14 @@ struct AvoidUnconditionalPreprocessorIfPPCallbacks : public 
PPCallbacks {
     if (Loc.isMacroID())
       return false;
 
-    Token Tok;
-    if (Lexer::getRawToken(Loc, Tok, SM, LangOpts, true)) {
-      std::optional<Token> TokOpt =
-          utils::lexer::findNextTokenSkippingComments(Loc, SM, LangOpts);
-      if (!TokOpt || TokOpt->getLocation().isMacroID())
-        return false;
-      Tok = *TokOpt;
-    }
-
-    while (Tok.getLocation() <= ConditionRange.getEnd()) {
+    const std::vector<Token> Tokens = utils::lexer::getRawTokens(
+        CharSourceRange::getTokenRange(ConditionRange), SM, LangOpts);
+    if (Tokens.empty())
+      return false;
+    for (const Token &Tok : Tokens)
       if (!isImmutableToken(Tok))
         return false;
 
-      std::optional<Token> TokOpt = 
utils::lexer::findNextTokenSkippingComments(
-          Tok.getLocation(), SM, LangOpts);
-      if (!TokOpt || TokOpt->getLocation().isMacroID())
-        return false;
-      Tok = *TokOpt;
-    }
-
     return true;
   }
 
diff --git 
a/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp
 
b/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp
index c75243784d5bef..ebc6c7c5ba574e 100644
--- 
a/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp
+++ 
b/clang-tools-extra/clang-tidy/readability/UseConcisePreprocessorDirectivesCheck.cpp
@@ -7,8 +7,8 @@
 
//===----------------------------------------------------------------------===//
 
 #include "UseConcisePreprocessorDirectivesCheck.h"
+#include "../utils/LexerUtils.h"
 #include "clang/Basic/TokenKinds.h"
-#include "clang/Lex/Lexer.h"
 #include "clang/Lex/PPCallbacks.h"
 #include "clang/Lex/Preprocessor.h"
 
@@ -16,6 +16,8 @@
 
 namespace clang::tidy::readability {
 
+using utils::lexer::getTokenName;
+
 namespace {
 
 class IfPreprocessorCallbacks final : public PPCallbacks {
@@ -37,50 +39,47 @@ class IfPreprocessorCallbacks final : public PPCallbacks {
 private:
   void impl(SourceLocation DirectiveLoc, SourceRange ConditionRange,
             const std::array<StringRef, 2> &Replacements) {
-    // Lexer requires its input range to be null-terminated.
-    SmallString<128> Condition =
-        Lexer::getSourceText(CharSourceRange::getTokenRange(ConditionRange),
-                             PP.getSourceManager(), PP.getLangOpts());
-    Condition.push_back('\0');
-    Lexer Lex(DirectiveLoc, PP.getLangOpts(), Condition.data(),
-              Condition.data(), Condition.data() + Condition.size() - 1);
-    Token Tok;
+    const std::vector<Token> Tokens = utils::lexer::getRawTokens(
+        CharSourceRange::getTokenRange(ConditionRange), PP.getSourceManager(),
+        PP.getLangOpts());
     bool Inverted = false; // The inverted form of #*def is #*ndef.
     std::size_t ParensNestingDepth = 0;
-    for (;;) {
-      if (Lex.LexFromRawLexer(Tok))
-        return;
-
+    std::size_t Index = 0;
+    while (Index < Tokens.size()) {
+      const Token &Tok = Tokens[Index];
       if (Tok.is(tok::TokenKind::exclaim) ||
           (PP.getLangOpts().CPlusPlus &&
            Tok.is(tok::TokenKind::raw_identifier) &&
-           Tok.getRawIdentifier() == "not"))
+           getTokenName(Tok) == "not")) {
         Inverted = !Inverted;
-      else if (Tok.is(tok::TokenKind::l_paren))
+        ++Index;
+      } else if (Tok.is(tok::TokenKind::l_paren)) {
         ++ParensNestingDepth;
-      else
+        ++Index;
+      } else {
         break;
+      }
     }
 
-    if (Tok.isNot(tok::TokenKind::raw_identifier) ||
-        Tok.getRawIdentifier() != "defined")
+    if (Index >= Tokens.size() ||
+        Tokens[Index].isNot(tok::TokenKind::raw_identifier) ||
+        getTokenName(Tokens[Index]) != "defined")
       return;
+    ++Index;
 
-    bool NoMoreTokens = Lex.LexFromRawLexer(Tok);
-    if (Tok.is(tok::TokenKind::l_paren)) {
-      if (NoMoreTokens)
-        return;
+    if (Index < Tokens.size() && Tokens[Index].is(tok::TokenKind::l_paren)) {
       ++ParensNestingDepth;
-      NoMoreTokens = Lex.LexFromRawLexer(Tok);
+      ++Index;
     }
 
-    if (Tok.isNot(tok::TokenKind::raw_identifier))
+    if (Index >= Tokens.size() ||
+        Tokens[Index].isNot(tok::TokenKind::raw_identifier))
       return;
-    const StringRef Macro = Tok.getRawIdentifier();
+    const StringRef Macro = getTokenName(Tokens[Index++]);
 
-    while (!NoMoreTokens) {
-      NoMoreTokens = Lex.LexFromRawLexer(Tok);
-      if (Tok.isNot(tok::TokenKind::r_paren))
+    while (Index < Tokens.size()) {
+      if (Tokens[Index++].isNot(tok::TokenKind::r_paren) ||
+          ParensNestingDepth == 0)
         return;
       --ParensNestingDepth;
     }
diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp 
b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
index d83713f06b8003..c3d907c9ca6574 100644
--- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
+++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
@@ -14,6 +14,46 @@
 
 namespace clang::tidy::utils::lexer {
 
+StringRef getTokenName(const Token &Tok) {
+  if (Tok.is(tok::raw_identifier))
+    return Tok.getRawIdentifier();
+  if (const IdentifierInfo *Info = Tok.getIdentifierInfo())
+    return Info->getName();
+  return {};
+}
+
+std::vector<Token> getRawTokens(CharSourceRange Range, const SourceManager &SM,
+                                const LangOptions &LangOpts) {
+  if (Range.isInvalid())
+    return {};
+
+  const CharSourceRange FileRange =
+      Lexer::makeFileCharRange(Range, SM, LangOpts);
+  if (FileRange.isInvalid())
+    return {};
+
+  bool Invalid = false;
+  const StringRef Text =
+      Lexer::getSourceText(FileRange, SM, LangOpts, &Invalid);
+  if (Invalid || Text.empty())
+    return {};
+
+  const auto [File, BeginOffset] = SM.getDecomposedLoc(FileRange.getBegin());
+  const StringRef Buffer = SM.getBufferData(File, &Invalid);
+  if (Invalid || BeginOffset + Text.size() > Buffer.size())
+    return {};
+
+  Lexer RawLexer(SM.getLocForStartOfFile(File), LangOpts, Buffer.begin(),
+                 Buffer.begin() + BeginOffset, Buffer.end());
+  const size_t EndOffset = BeginOffset + Text.size();
+  std::vector<Token> Tokens;
+  Token Tok;
+  while (!RawLexer.LexFromRawLexer(Tok) && Tok.isNot(tok::eof) &&
+         SM.getFileOffset(Tok.getLocation()) < EndOffset)
+    Tokens.push_back(Tok);
+  return Tokens;
+}
+
 std::pair<std::optional<Token>, SourceLocation>
 getPreviousTokenAndStart(SourceLocation Location, const SourceManager &SM,
                          const LangOptions &LangOpts, bool SkipComments) {
diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h 
b/clang-tools-extra/clang-tidy/utils/LexerUtils.h
index 9b2daf74965e41..1d820ad92652a1 100644
--- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h
+++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h
@@ -23,6 +23,14 @@ class Stmt;
 
 namespace tidy::utils::lexer {
 
+/// Returns the spelling of an identifier token.
+StringRef getTokenName(const Token &Tok);
+
+/// Returns the raw tokens in \p Range. The returned tokens refer to the
+/// source manager's backing buffer.
+std::vector<Token> getRawTokens(CharSourceRange Range, const SourceManager &SM,
+                                const LangOptions &LangOpts);
+
 /// Returns previous token or ``std::nullopt`` if not found.
 std::optional<Token> getPreviousToken(SourceLocation Location,
                                       const SourceManager &SM,
diff --git a/clang-tools-extra/docs/ReleaseNotes.md 
b/clang-tools-extra/docs/ReleaseNotes.md
index 0207a72fc6cce0..de5d3c1e283201 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -141,6 +141,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 00000000000000..a3f6d2286e3632
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/macro-condition.md
@@ -0,0 +1,100 @@
+```{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
+// ...
+#if USE_FOO
+  // ...
+#endif
+```
+
+`USE_FOO` is checked for definition in one condition and for value in
+another. Was the intention to evaluate `USE_FOO` for a `true` expression,
+or was the intention to merely check whether the macro was defined?
+
+The check compares uses only when they refer to the same active definition
+of an object-like macro with a nonempty replacement list and occur in the
+same physical source file. It emits at most one warning for each macro
+definition in each file. Merely defining a macro with a value and testing
+its definition does not produce a warning by itself.
+
+## Excluded scenarios
+
+The check excludes the following scenarios:
+
+- Uses in different physical source files are not compared.
+- Uses resolving to different active macro definitions are not compared.
+  Consequently, uses separated by ``#undef`` or a redefinition are not
+  combined.
+- Undefined macros, including macros undefined with command-line ``-U``,
+  are ignored. Value tests of undefined macros are handled by `-Wundef`.
+- Function-like macros and function-like invocations are ignored. This
+  includes preprocessing operators such as `__has_builtin`,
+  `__has_include`, and `__has_cpp_attribute`, as well as identifiers in
+  their argument lists.
+- Compiler-provided builtin macros are ignored.
+- Standard predefined macros are ignored. These are `__cplusplus`,
+  `__DATE__`, `__FILE__`, `__LINE__`, `__TIME__`, and names beginning with
+  `__cpp_`, `__STDC_`, or `__STDCPP_`.
+- Object-like macros with empty replacement lists are ignored.
+- Negated definition tests are ignored. These include ``#ifndef``,
+  ``#elifndef``, and ``!defined(FEATURE)``.
+- A compound condition that tests the same macro for both definition and
+  value is treated as one coherent test and ignored.
+- A value test nested in a same-file positive definition guard is ignored.
+  Qualifying guards are ``#ifdef``, ``#elifdef``, and ``#if`` or ``#elif``
+  expressions consisting solely of positive definition tests joined by
+  ``&&``. Disjunctions and ``#else`` branches do not establish a guard.
+- A guard that supplies a default value for its macro is not considered a
+  definition test. This applies to both ``#ifndef FEATURE`` and
+  ``#if !defined(FEATURE)`` forms.
+- A condition whose branch contains a top-level ``#error`` directive is
+  treated as configuration validation and ignored. An ``#error`` inside a
+  nested conditional is not unconditional and does not suppress a warning.
+- Non-macro language tokens such as `true`, `false`, and C++ operator
+  keywords are not considered macro references.
+
+For example, a value test nested inside a positive definition guard does
+not qualify for a warning:
+
+```c++
+#ifdef FEATURE
+#if FEATURE >= 2
+  // ...
+#endif
+#endif
+```
+
+A guard that supplies a default value also does not qualify:
+
+```c++
+#ifndef FEATURE
+#define FEATURE 0
+#endif
+
+#if FEATURE
+  // ...
+#endif
+```
+
+No fixes are offered because the intended semantics are ambiguous.
+
+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.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.md 
b/clang-tools-extra/docs/clang-tidy/checks/list.md
index a74a26e691053d..8fd3d7408c9f5c 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/Inputs/macro-condition-cross-file.h
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/macro-condition-cross-file.h
new file mode 100644
index 00000000000000..13418c41a6e8fb
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/macro-condition-cross-file.h
@@ -0,0 +1,3 @@
+#ifndef CROSS_FILE_MACRO
+#define CROSS_FILE_MACRO 1
+#endif
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 00000000000000..6ceb899592473e
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-command-line.cpp
@@ -0,0 +1,19 @@
+// RUN: %check_clang_tidy -check-suffix=DEFINED %s \
+// RUN:   bugprone-macro-condition %t -- -- -DCOMMAND_LINE_MACRO=0
+// RUN: clang-tidy %s -checks=-*,bugprone-macro-condition -- \
+// RUN:   -UCOMMAND_LINE_MACRO | count 0
+
+// With -UCOMMAND_LINE_MACRO, these conditions are equivalent to:
+//
+// #undef COMMAND_LINE_MACRO
+
+// With -DCOMMAND_LINE_MACRO=0, they are equivalent to:
+//
+// #define COMMAND_LINE_MACRO 0
+#ifdef COMMAND_LINE_MACRO
+#endif
+
+#if COMMAND_LINE_MACRO
+// CHECK-MESSAGES-DEFINED: :[[@LINE-1]]:5: warning: Macro 'COMMAND_LINE_MACRO' 
checked here for value after being checked for definition
+// CHECK-MESSAGES-DEFINED: :[[@LINE-5]]:2: note: Macro 'COMMAND_LINE_MACRO' 
first checked here for definition
+#endif
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-cross-file.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-cross-file.cpp
new file mode 100644
index 00000000000000..4fbb2e8fc29b36
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition-cross-file.cpp
@@ -0,0 +1,8 @@
+// RUN: clang-tidy %s -checks=-*,bugprone-macro-condition -- -I %S | count 0
+
+#define CROSS_FILE_MACRO 1
+#include "Inputs/macro-condition-cross-file.h"
+
+#if CROSS_FILE_MACRO
+#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 00000000000000..f48b4555a04b93
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/macro-condition.cpp
@@ -0,0 +1,213 @@
+// RUN: %check_clang_tidy %s bugprone-macro-condition %t
+
+#define USE_FOO 0
+
+#if defined(USE_FOO)
+void f()
+{
+  extern void foo();
+  foo();
+}
+#endif
+
+#define VALUE_DEFINED 42
+#ifndef VALUE_DEFINED
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'VALUE_DEFINED'
+
+#if 0
+#elif OTHER_MACRO
+#elifdef OTHER_MACRO2
+#else
+#endif
+
+#if !defined(USE_FOO)
+void f2()
+{
+  extern void notFoo();
+  notFoo();
+}
+#endif
+
+#ifdef USE_FOO
+void f3()
+{
+  extern void foo();
+  foo();
+}
+#endif
+
+#ifndef USE_FOO
+void f4()
+{
+  extern void notFoo();
+  notFoo();
+}
+#endif
+
+#if 0
+#elif defined(USE_FOO)
+void f5()
+{
+  extern void foo();
+  foo();
+}
+#endif
+
+// CHECK-MESSAGES-NOT: warning: Macro 'USE_FOO'
+// CHECK-MESSAGES-NOT: warning: Undefined macro 'OTHER_MACRO'
+
+#define USE_GRONK 0
+#ifdef USE_GRONK
+#if USE_GRONK
+void f6()
+{
+  extern void foo();
+  foo();
+}
+#endif
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'USE_GRONK'
+
+#define ENCLOSED_DEFINED 1
+#if defined(ENCLOSED_DEFINED)
+#if ENCLOSED_DEFINED
+#endif
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'ENCLOSED_DEFINED'
+
+#define ENCLOSED_CONJUNCTION 1
+#define ENCLOSING_SUPPORT 1
+#if defined(ENCLOSED_CONJUNCTION) && defined(ENCLOSING_SUPPORT)
+#if ENCLOSED_CONJUNCTION
+#endif
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'ENCLOSED_CONJUNCTION'
+
+#define VALIDATED_VALUE 100
+#ifdef VALIDATED_VALUE
+#endif
+#if VALIDATED_VALUE < 10
+#error VALIDATED_VALUE is too small.
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'VALIDATED_VALUE'
+
+#define CONDITIONAL_ERROR 1
+#ifdef CONDITIONAL_ERROR
+#endif
+#if CONDITIONAL_ERROR
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Macro 'CONDITIONAL_ERROR' checked 
here for value after being checked for definition
+// CHECK-MESSAGES: :[[@LINE-4]]:2: note: Macro 'CONDITIONAL_ERROR' first 
checked here for definition
+#if 0
+#error This error is conditional.
+#endif
+#endif
+
+#define VALUE_FIRST 0
+#if VALUE_FIRST
+#endif
+#ifndef VALUE_FIRST
+void f7()
+{
+  extern void foo();
+  foo();
+}
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'VALUE_FIRST'
+
+#define REQUIRED_OPTION 1
+#define REQUIRED_NAME required_namespace
+#if !defined(REQUIRED_OPTION) || \
+    !defined(REQUIRED_NAME)
+#error Required options are not configured.
+#endif
+#if defined(__cplusplus) && REQUIRED_OPTION == 1
+#endif
+// CHECK-MESSAGES-NOT: warning: Macro 'REQUIRED_OPTION'
+
+#define POSITIVE_DEFINITION 1
+#define OTHER_POSITIVE_DEFINITION 1
+#if defined(POSITIVE_DEFINITION) || defined(OTHER_POSITIVE_DEFINITION)
+#endif
+#if POSITIVE_DEFINITION
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Macro 'POSITIVE_DEFINITION' 
checked here for value after being checked for definition
+// CHECK-MESSAGES: :[[@LINE-4]]:5: note: Macro 'POSITIVE_DEFINITION' first 
checked here for definition
+#endif
+
+#define SAME_CONDITION 0
+#if defined(SAME_CONDITION) && SAME_CONDITION
+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)
+#undef GUARDED
+#if GUARDED
+#endif
+#endif
+// CHECK-MESSAGES-NOT: warning: {{.*}}'GUARDED'
+
+#define GUARDED_CONJUNCTION 1
+#ifdef GUARDED_CONJUNCTION
+#endif
+#if defined(GUARDED_CONJUNCTION) && GUARDED_CONJUNCTION
+#endif
+
+#define GUARDED_DISJUNCTION 1
+#ifdef GUARDED_DISJUNCTION
+#endif
+#if !defined(GUARDED_DISJUNCTION) || GUARDED_DISJUNCTION
+#endif
+
+#ifdef __cplusplus
+#endif
+#if __cplusplus >= 201103L
+#endif
+
+#ifdef __STDC_HOSTED__
+#endif
+#if __STDC_HOSTED__
+#endif
+
+#define DEFAULT_IFNDEF 1
+#ifndef DEFAULT_IFNDEF
+#define DEFAULT_IFNDEF 0
+#endif
+#if DEFAULT_IFNDEF
+#endif
+
+#define DEFAULT_NOT_DEFINED 1
+#if !defined(DEFAULT_NOT_DEFINED)
+#define DEFAULT_NOT_DEFINED 0
+#endif
+#if DEFAULT_NOT_DEFINED
+#endif
diff --git a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp 
b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
index 485b527c281a2c..2f63a1db1865cb 100644
--- a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
@@ -48,6 +48,30 @@ static bool isRawIdentifierNamed(const Token &Tok, StringRef 
Name) {
 
 namespace {
 
+TEST(LexerUtilsTest, GetRawTokens) {
+  llvm::Annotations Code(R"cpp(
+void f() {
+  int alpha = 0;
+  int beta = 0;
+  bool value = $range[[alpha /* ignored */ && beta]];
+}
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange Range =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const std::vector<Token> Tokens =
+      utils::lexer::getRawTokens(Range, SM, LangOpts);
+  ASSERT_EQ(3u, Tokens.size());
+  EXPECT_EQ("alpha", utils::lexer::getTokenName(Tokens[0]));
+  EXPECT_TRUE(Tokens[1].is(tok::ampamp));
+  EXPECT_EQ("beta", utils::lexer::getTokenName(Tokens[2]));
+}
+
 TEST(LexerUtilsTest, GetCommentsInRangeAdjacentComments) {
   llvm::Annotations Code(R"cpp(
 void f() {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to