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