xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp (revision 11a411a49b62c129bba551df4587dd446fcdc660)
1 //===--- MultipleStatementMacroCheck.cpp - clang-tidy----------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "MultipleStatementMacroCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang::tidy::bugprone {
16 
17 namespace {
18 
AST_MATCHER(Expr,isInMacro)19 AST_MATCHER(Expr, isInMacro) { return Node.getBeginLoc().isMacroID(); }
20 
21 /// Find the next statement after `S`.
nextStmt(const MatchFinder::MatchResult & Result,const Stmt * S)22 const Stmt *nextStmt(const MatchFinder::MatchResult &Result, const Stmt *S) {
23   auto Parents = Result.Context->getParents(*S);
24   if (Parents.empty())
25     return nullptr;
26   const auto *Parent = Parents[0].get<Stmt>();
27   if (!Parent)
28     return nullptr;
29   const Stmt *Prev = nullptr;
30   for (const Stmt *Child : Parent->children()) {
31     if (Prev == S)
32       return Child;
33     Prev = Child;
34   }
35   return nextStmt(Result, Parent);
36 }
37 
38 using ExpansionRanges = std::vector<SourceRange>;
39 
40 /// \brief Get all the macro expansion ranges related to `Loc`.
41 ///
42 /// The result is ordered from most inner to most outer.
getExpansionRanges(SourceLocation Loc,const MatchFinder::MatchResult & Result)43 ExpansionRanges getExpansionRanges(SourceLocation Loc,
44                                    const MatchFinder::MatchResult &Result) {
45   ExpansionRanges Locs;
46   while (Loc.isMacroID()) {
47     Locs.push_back(
48         Result.SourceManager->getImmediateExpansionRange(Loc).getAsRange());
49     Loc = Locs.back().getBegin();
50   }
51   return Locs;
52 }
53 
54 } // namespace
55 
registerMatchers(MatchFinder * Finder)56 void MultipleStatementMacroCheck::registerMatchers(MatchFinder *Finder) {
57   const auto Inner = expr(isInMacro(), unless(compoundStmt())).bind("inner");
58   Finder->addMatcher(
59       stmt(anyOf(ifStmt(hasThen(Inner)), ifStmt(hasElse(Inner)).bind("else"),
60                  whileStmt(hasBody(Inner)), forStmt(hasBody(Inner))))
61           .bind("outer"),
62       this);
63 }
64 
check(const MatchFinder::MatchResult & Result)65 void MultipleStatementMacroCheck::check(
66     const MatchFinder::MatchResult &Result) {
67   const auto *Inner = Result.Nodes.getNodeAs<Expr>("inner");
68   const auto *Outer = Result.Nodes.getNodeAs<Stmt>("outer");
69   const auto *Next = nextStmt(Result, Outer);
70   if (!Next)
71     return;
72 
73   SourceLocation OuterLoc = Outer->getBeginLoc();
74   if (Result.Nodes.getNodeAs<Stmt>("else"))
75     OuterLoc = cast<IfStmt>(Outer)->getElseLoc();
76 
77   auto InnerRanges = getExpansionRanges(Inner->getBeginLoc(), Result);
78   auto OuterRanges = getExpansionRanges(OuterLoc, Result);
79   auto NextRanges = getExpansionRanges(Next->getBeginLoc(), Result);
80 
81   // Remove all the common ranges, starting from the top (the last ones in the
82   // list).
83   while (!InnerRanges.empty() && !OuterRanges.empty() && !NextRanges.empty() &&
84          InnerRanges.back() == OuterRanges.back() &&
85          InnerRanges.back() == NextRanges.back()) {
86     InnerRanges.pop_back();
87     OuterRanges.pop_back();
88     NextRanges.pop_back();
89   }
90 
91   // Inner and Next must have at least one more macro that Outer doesn't have,
92   // and that range must be common to both.
93   if (InnerRanges.empty() || NextRanges.empty() ||
94       InnerRanges.back() != NextRanges.back())
95     return;
96 
97   diag(InnerRanges.back().getBegin(), "multiple statement macro used without "
98                                       "braces; some statements will be "
99                                       "unconditionally executed");
100 }
101 
102 } // namespace clang::tidy::bugprone
103