1 //===--- MacroRepeatedSideEffectsCheck.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 "MacroRepeatedSideEffectsCheck.h"
10 #include "clang/Basic/Builtins.h"
11 #include "clang/Frontend/CompilerInstance.h"
12 #include "clang/Lex/MacroArgs.h"
13 #include "clang/Lex/PPCallbacks.h"
14 #include "clang/Lex/Preprocessor.h"
15 
16 namespace clang {
17 namespace tidy {
18 namespace bugprone {
19 
20 namespace {
21 class MacroRepeatedPPCallbacks : public PPCallbacks {
22 public:
MacroRepeatedPPCallbacks(ClangTidyCheck & Check,Preprocessor & PP)23   MacroRepeatedPPCallbacks(ClangTidyCheck &Check, Preprocessor &PP)
24       : Check(Check), PP(PP) {}
25 
26   void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
27                     SourceRange Range, const MacroArgs *Args) override;
28 
29 private:
30   ClangTidyCheck &Check;
31   Preprocessor &PP;
32 
33   unsigned countArgumentExpansions(const MacroInfo *MI,
34                                    const IdentifierInfo *Arg) const;
35 
36   bool hasSideEffects(const Token *ResultArgToks) const;
37 };
38 } // End of anonymous namespace.
39 
MacroExpands(const Token & MacroNameTok,const MacroDefinition & MD,SourceRange Range,const MacroArgs * Args)40 void MacroRepeatedPPCallbacks::MacroExpands(const Token &MacroNameTok,
41                                             const MacroDefinition &MD,
42                                             SourceRange Range,
43                                             const MacroArgs *Args) {
44   // Ignore macro argument expansions.
45   if (!Range.getBegin().isFileID())
46     return;
47 
48   const MacroInfo *MI = MD.getMacroInfo();
49 
50   // Bail out if the contents of the macro are containing keywords that are
51   // making the macro too complex.
52   if (std::find_if(
53           MI->tokens().begin(), MI->tokens().end(), [](const Token &T) {
54             return T.isOneOf(tok::kw_if, tok::kw_else, tok::kw_switch,
55                              tok::kw_case, tok::kw_break, tok::kw_while,
56                              tok::kw_do, tok::kw_for, tok::kw_continue,
57                              tok::kw_goto, tok::kw_return);
58           }) != MI->tokens().end())
59     return;
60 
61   for (unsigned ArgNo = 0U; ArgNo < MI->getNumParams(); ++ArgNo) {
62     const IdentifierInfo *Arg = *(MI->param_begin() + ArgNo);
63     const Token *ResultArgToks = Args->getUnexpArgument(ArgNo);
64 
65     if (hasSideEffects(ResultArgToks) &&
66         countArgumentExpansions(MI, Arg) >= 2) {
67       Check.diag(ResultArgToks->getLocation(),
68                  "side effects in the %ordinal0 macro argument %1 are "
69                  "repeated in macro expansion")
70           << (ArgNo + 1) << Arg;
71       Check.diag(MI->getDefinitionLoc(), "macro %0 defined here",
72                  DiagnosticIDs::Note)
73           << MacroNameTok.getIdentifierInfo();
74     }
75   }
76 }
77 
countArgumentExpansions(const MacroInfo * MI,const IdentifierInfo * Arg) const78 unsigned MacroRepeatedPPCallbacks::countArgumentExpansions(
79     const MacroInfo *MI, const IdentifierInfo *Arg) const {
80   // Current argument count. When moving forward to a different control-flow
81   // path this can decrease.
82   unsigned Current = 0;
83   // Max argument count.
84   unsigned Max = 0;
85   bool SkipParen = false;
86   int SkipParenCount = 0;
87   // Has a __builtin_constant_p been found?
88   bool FoundBuiltin = false;
89   bool PrevTokenIsHash = false;
90   // Count when "?" is reached. The "Current" will get this value when the ":"
91   // is reached.
92   std::stack<unsigned, SmallVector<unsigned, 8>> CountAtQuestion;
93   for (const auto &T : MI->tokens()) {
94     // The result of __builtin_constant_p(x) is 0 if x is a macro argument
95     // with side effects. If we see a __builtin_constant_p(x) followed by a
96     // "?" "&&" or "||", then we need to reason about control flow to report
97     // warnings correctly. Until such reasoning is added, bail out when this
98     // happens.
99     if (FoundBuiltin && T.isOneOf(tok::question, tok::ampamp, tok::pipepipe))
100       return Max;
101 
102     // Skip stringified tokens.
103     if (T.is(tok::hash)) {
104       PrevTokenIsHash = true;
105       continue;
106     }
107     if (PrevTokenIsHash) {
108       PrevTokenIsHash = false;
109       continue;
110     }
111 
112     // Handling of ? and :.
113     if (T.is(tok::question)) {
114       CountAtQuestion.push(Current);
115     } else if (T.is(tok::colon)) {
116       if (CountAtQuestion.empty())
117         return 0;
118       Current = CountAtQuestion.top();
119       CountAtQuestion.pop();
120     }
121 
122     // If current token is a parenthesis, skip it.
123     if (SkipParen) {
124       if (T.is(tok::l_paren))
125         SkipParenCount++;
126       else if (T.is(tok::r_paren))
127         SkipParenCount--;
128       SkipParen = (SkipParenCount != 0);
129       if (SkipParen)
130         continue;
131     }
132 
133     IdentifierInfo *TII = T.getIdentifierInfo();
134     // If not existent, skip it.
135     if (TII == nullptr)
136       continue;
137 
138     // If a __builtin_constant_p is found within the macro definition, don't
139     // count arguments inside the parentheses and remember that it has been
140     // seen in case there are "?", "&&" or "||" operators later.
141     if (TII->getBuiltinID() == Builtin::BI__builtin_constant_p) {
142       FoundBuiltin = true;
143       SkipParen = true;
144       continue;
145     }
146 
147     // If another macro is found within the macro definition, skip the macro
148     // and the eventual arguments.
149     if (TII->hasMacroDefinition()) {
150       const MacroInfo *M = PP.getMacroDefinition(TII).getMacroInfo();
151       if (M != nullptr && M->isFunctionLike())
152         SkipParen = true;
153       continue;
154     }
155 
156     // Count argument.
157     if (TII == Arg) {
158       Current++;
159       if (Current > Max)
160         Max = Current;
161     }
162   }
163   return Max;
164 }
165 
hasSideEffects(const Token * ResultArgToks) const166 bool MacroRepeatedPPCallbacks::hasSideEffects(
167     const Token *ResultArgToks) const {
168   for (; ResultArgToks->isNot(tok::eof); ++ResultArgToks) {
169     if (ResultArgToks->isOneOf(tok::plusplus, tok::minusminus))
170       return true;
171   }
172   return false;
173 }
174 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)175 void MacroRepeatedSideEffectsCheck::registerPPCallbacks(
176     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
177   PP->addPPCallbacks(::std::make_unique<MacroRepeatedPPCallbacks>(*this, *PP));
178 }
179 
180 } // namespace bugprone
181 } // namespace tidy
182 } // namespace clang
183