| //===--- MultipleStatementMacroCheck.cpp - clang-tidy----------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #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.getLocStart().isMacroID(); } |
| |
| /// \brief 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<Stmt>(); |
| 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<SourceRange>; |
| |
| /// \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<Expr>("inner"); |
| const auto *Outer = Result.Nodes.getNodeAs<Stmt>("outer"); |
| const auto *Next = nextStmt(Result, Outer); |
| if (!Next) |
| return; |
| |
| SourceLocation OuterLoc = Outer->getLocStart(); |
| if (Result.Nodes.getNodeAs<Stmt>("else")) |
| OuterLoc = cast<IfStmt>(Outer)->getElseLoc(); |
| |
| auto InnerRanges = getExpansionRanges(Inner->getLocStart(), Result); |
| auto OuterRanges = getExpansionRanges(OuterLoc, Result); |
| auto NextRanges = getExpansionRanges(Next->getLocStart(), 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 |