1 //===--- MacroRepeatedSideEffectsCheck.cpp - clang-tidy--------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
16 namespace clang::tidy::bugprone
{
19 class MacroRepeatedPPCallbacks
: public PPCallbacks
{
21 MacroRepeatedPPCallbacks(ClangTidyCheck
&Check
, Preprocessor
&PP
)
22 : Check(Check
), PP(PP
) {}
24 void MacroExpands(const Token
&MacroNameTok
, const MacroDefinition
&MD
,
25 SourceRange Range
, const MacroArgs
*Args
) override
;
28 ClangTidyCheck
&Check
;
31 unsigned countArgumentExpansions(const MacroInfo
*MI
,
32 const IdentifierInfo
*Arg
) const;
34 bool hasSideEffects(const Token
*ResultArgToks
) const;
36 } // End of anonymous namespace.
38 void MacroRepeatedPPCallbacks::MacroExpands(const Token
&MacroNameTok
,
39 const MacroDefinition
&MD
,
41 const MacroArgs
*Args
) {
42 // Ignore macro argument expansions.
43 if (!Range
.getBegin().isFileID())
46 const MacroInfo
*MI
= MD
.getMacroInfo();
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(MI
->tokens(), [](const Token
&T
) {
51 return T
.isOneOf(tok::kw_if
, tok::kw_else
, tok::kw_switch
, tok::kw_case
,
52 tok::kw_break
, tok::kw_while
, tok::kw_do
, tok::kw_for
,
53 tok::kw_continue
, tok::kw_goto
, tok::kw_return
);
57 for (unsigned ArgNo
= 0U; ArgNo
< MI
->getNumParams(); ++ArgNo
) {
58 const IdentifierInfo
*Arg
= *(MI
->param_begin() + ArgNo
);
59 const Token
*ResultArgToks
= Args
->getUnexpArgument(ArgNo
);
61 if (hasSideEffects(ResultArgToks
) &&
62 countArgumentExpansions(MI
, Arg
) >= 2) {
63 Check
.diag(ResultArgToks
->getLocation(),
64 "side effects in the %ordinal0 macro argument %1 are "
65 "repeated in macro expansion")
66 << (ArgNo
+ 1) << Arg
;
67 Check
.diag(MI
->getDefinitionLoc(), "macro %0 defined here",
69 << MacroNameTok
.getIdentifierInfo();
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.
79 // Max argument count.
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 ":"
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
95 if (FoundBuiltin
&& T
.isOneOf(tok::question
, tok::ampamp
, tok::pipepipe
))
98 // Skip stringified tokens.
99 if (T
.is(tok::hash
)) {
100 PrevTokenIsHash
= true;
103 if (PrevTokenIsHash
) {
104 PrevTokenIsHash
= false;
108 // Handling of ? and :.
109 if (T
.is(tok::question
)) {
110 CountAtQuestion
.push(Current
);
111 } else if (T
.is(tok::colon
)) {
112 if (CountAtQuestion
.empty())
114 Current
= CountAtQuestion
.top();
115 CountAtQuestion
.pop();
118 // If current token is a parenthesis, skip it.
120 if (T
.is(tok::l_paren
))
122 else if (T
.is(tok::r_paren
))
124 SkipParen
= (SkipParenCount
!= 0);
129 IdentifierInfo
*TII
= T
.getIdentifierInfo();
130 // If not existent, skip it.
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
) {
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(TII
).getMacroInfo();
147 if (M
!= nullptr && M
->isFunctionLike())
162 bool MacroRepeatedPPCallbacks::hasSideEffects(
163 const Token
*ResultArgToks
) const {
164 for (; ResultArgToks
->isNot(tok::eof
); ++ResultArgToks
) {
165 if (ResultArgToks
->isOneOf(tok::plusplus
, tok::minusminus
))
171 void MacroRepeatedSideEffectsCheck::registerPPCallbacks(
172 const SourceManager
&SM
, Preprocessor
*PP
, Preprocessor
*ModuleExpanderPP
) {
173 PP
->addPPCallbacks(::std::make_unique
<MacroRepeatedPPCallbacks
>(*this, *PP
));
176 } // namespace clang::tidy::bugprone