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