| 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 | |