//===--- MultipleStatementMacroCheck.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 "MultipleStatementMacroCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace bugprone { namespace { AST_MATCHER(Expr, isInMacro) { return Node.getBeginLoc().isMacroID(); } /// Find the next statement after `S`. const Stmt *nextStmt(const MatchFinder::MatchResult &Result, const Stmt *S) { auto Parents = Result.Context->getParents(*S); if (Parents.empty()) return nullptr; const auto *Parent = Parents[0].get(); if (!Parent) return nullptr; const Stmt *Prev = nullptr; for (const Stmt *Child : Parent->children()) { if (Prev == S) return Child; Prev = Child; } return nextStmt(Result, Parent); } using ExpansionRanges = std::vector; /// \bried Get all the macro expansion ranges related to `Loc`. /// /// The result is ordered from most inner to most outer. ExpansionRanges getExpansionRanges(SourceLocation Loc, const MatchFinder::MatchResult &Result) { ExpansionRanges Locs; while (Loc.isMacroID()) { Locs.push_back( Result.SourceManager->getImmediateExpansionRange(Loc).getAsRange()); Loc = Locs.back().getBegin(); } return Locs; } } // namespace void MultipleStatementMacroCheck::registerMatchers(MatchFinder *Finder) { const auto Inner = expr(isInMacro(), unless(compoundStmt())).bind("inner"); Finder->addMatcher( stmt(anyOf(ifStmt(hasThen(Inner)), ifStmt(hasElse(Inner)).bind("else"), whileStmt(hasBody(Inner)), forStmt(hasBody(Inner)))) .bind("outer"), this); } void MultipleStatementMacroCheck::check( const MatchFinder::MatchResult &Result) { const auto *Inner = Result.Nodes.getNodeAs("inner"); const auto *Outer = Result.Nodes.getNodeAs("outer"); const auto *Next = nextStmt(Result, Outer); if (!Next) return; SourceLocation OuterLoc = Outer->getBeginLoc(); if (Result.Nodes.getNodeAs("else")) OuterLoc = cast(Outer)->getElseLoc(); auto InnerRanges = getExpansionRanges(Inner->getBeginLoc(), Result); auto OuterRanges = getExpansionRanges(OuterLoc, Result); auto NextRanges = getExpansionRanges(Next->getBeginLoc(), Result); // Remove all the common ranges, starting from the top (the last ones in the // list). while (!InnerRanges.empty() && !OuterRanges.empty() && !NextRanges.empty() && InnerRanges.back() == OuterRanges.back() && InnerRanges.back() == NextRanges.back()) { InnerRanges.pop_back(); OuterRanges.pop_back(); NextRanges.pop_back(); } // Inner and Next must have at least one more macro that Outer doesn't have, // and that range must be common to both. if (InnerRanges.empty() || NextRanges.empty() || InnerRanges.back() != NextRanges.back()) return; diag(InnerRanges.back().getBegin(), "multiple statement macro used without " "braces; some statements will be " "unconditionally executed"); } } // namespace bugprone } // namespace tidy } // namespace clang