clang-tools 20.0.0git
MacroRepeatedSideEffectsCheck.cpp
Go to the documentation of this file.
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
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
17namespace clang::tidy::bugprone {
18
19namespace {
20class MacroRepeatedPPCallbacks : public PPCallbacks {
21public:
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
28private:
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
39void 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
75unsigned 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
163bool 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
173 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
174 PP->addPPCallbacks(::std::make_unique<MacroRepeatedPPCallbacks>(*this, *PP));
175}
176
177} // namespace clang::tidy::bugprone
CharSourceRange Range
SourceRange for the file name.
const google::protobuf::Message & M
Definition: Server.cpp:309
llvm::json::Object Args
Definition: Trace.cpp:138
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.