177 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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 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 are50 // 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-flow78 // 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 argument92 // with side effects. If we see a __builtin_constant_p(x) followed by a93 // "?" "&&" or "||", then we need to reason about control flow to report94 // warnings correctly. Until such reasoning is added, bail out when this95 // 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 const 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't136 // count arguments inside the parentheses and remember that it has been137 // 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 macro145 // 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 Max = std::max(Max, Current);157 }158 }159 return Max;160}161 162bool MacroRepeatedPPCallbacks::hasSideEffects(163 const Token *ResultArgToks) const {164 for (; ResultArgToks->isNot(tok::eof); ++ResultArgToks) {165 if (ResultArgToks->isOneOf(tok::plusplus, 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(::std::make_unique<MacroRepeatedPPCallbacks>(*this, *PP));174}175 176} // namespace clang::tidy::bugprone177