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
16namespace clang::tidy::bugprone {
17
18namespace {
19class MacroRepeatedPPCallbacks : public PPCallbacks {
20public:
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
27private:
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
38void 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
74unsigned 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
162bool 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
171void 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

source code of clang-tools-extra/clang-tidy/bugprone/MacroRepeatedSideEffectsCheck.cpp