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: |
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 | |
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(Range: MI->tokens(), P: [](const Token &T) { |
52 | return T.isOneOf(K1: tok::kw_if, Ks: tok::kw_else, Ks: tok::kw_switch, Ks: tok::kw_case, |
53 | Ks: tok::kw_break, Ks: tok::kw_while, Ks: tok::kw_do, Ks: tok::kw_for, |
54 | Ks: tok::kw_continue, Ks: tok::kw_goto, Ks: 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(Arg: ArgNo); |
61 | |
62 | if (hasSideEffects(ResultArgToks) && |
63 | countArgumentExpansions(MI, Arg) >= 2) { |
64 | Check.diag(Loc: ResultArgToks->getLocation(), |
65 | Description: "side effects in the %ordinal0 macro argument %1 are " |
66 | "repeated in macro expansion" ) |
67 | << (ArgNo + 1) << Arg; |
68 | Check.diag(Loc: MI->getDefinitionLoc(), Description: "macro %0 defined here" , |
69 | Level: DiagnosticIDs::Note) |
70 | << MacroNameTok.getIdentifierInfo(); |
71 | } |
72 | } |
73 | } |
74 | |
75 | 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(K1: tok::question, Ks: tok::ampamp, Ks: tok::pipepipe)) |
97 | return Max; |
98 | |
99 | // Skip stringified tokens. |
100 | if (T.is(K: 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(K: tok::question)) { |
111 | CountAtQuestion.push(x: Current); |
112 | } else if (T.is(K: 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(K: tok::l_paren)) |
122 | SkipParenCount++; |
123 | else if (T.is(K: 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(II: 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 | |
163 | bool MacroRepeatedPPCallbacks::hasSideEffects( |
164 | const Token *ResultArgToks) const { |
165 | for (; ResultArgToks->isNot(K: tok::eof); ++ResultArgToks) { |
166 | if (ResultArgToks->isOneOf(K1: tok::plusplus, K2: tok::minusminus)) |
167 | return true; |
168 | } |
169 | return false; |
170 | } |
171 | |
172 | void MacroRepeatedSideEffectsCheck::registerPPCallbacks( |
173 | const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { |
174 | PP->addPPCallbacks(C: ::std::make_unique<MacroRepeatedPPCallbacks>(args&: *this, args&: *PP)); |
175 | } |
176 | |
177 | } // namespace clang::tidy::bugprone |
178 | |