blob: 245c03fe8e9c3f86d689663529766618cdbc86e4 [file] [log] [blame]
//===--- MacroRepeatedSideEffectsCheck.cpp - clang-tidy--------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "MacroRepeatedSideEffectsCheck.h"
#include "clang/Basic/Builtins.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/MacroArgs.h"
#include "clang/Lex/PPCallbacks.h"
#include "clang/Lex/Preprocessor.h"
namespace clang {
namespace tidy {
namespace bugprone {
namespace {
class MacroRepeatedPPCallbacks : public PPCallbacks {
public:
MacroRepeatedPPCallbacks(ClangTidyCheck &Check, Preprocessor &PP)
: Check(Check), PP(PP) {}
void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
SourceRange Range, const MacroArgs *Args) override;
private:
ClangTidyCheck &Check;
Preprocessor &PP;
unsigned countArgumentExpansions(const MacroInfo *MI,
const IdentifierInfo *Arg) const;
bool hasSideEffects(const Token *ResultArgToks) const;
};
} // End of anonymous namespace.
void MacroRepeatedPPCallbacks::MacroExpands(const Token &MacroNameTok,
const MacroDefinition &MD,
SourceRange Range,
const MacroArgs *Args) {
// Ignore macro argument expansions.
if (!Range.getBegin().isFileID())
return;
const MacroInfo *MI = MD.getMacroInfo();
// Bail out if the contents of the macro are containing keywords that are
// making the macro too complex.
if (std::find_if(
MI->tokens().begin(), MI->tokens().end(), [](const Token &T) {
return T.isOneOf(tok::kw_if, tok::kw_else, tok::kw_switch,
tok::kw_case, tok::kw_break, tok::kw_while,
tok::kw_do, tok::kw_for, tok::kw_continue,
tok::kw_goto, tok::kw_return);
}) != MI->tokens().end())
return;
for (unsigned ArgNo = 0U; ArgNo < MI->getNumParams(); ++ArgNo) {
const IdentifierInfo *Arg = *(MI->param_begin() + ArgNo);
const Token *ResultArgToks = Args->getUnexpArgument(ArgNo);
if (hasSideEffects(ResultArgToks) &&
countArgumentExpansions(MI, Arg) >= 2) {
Check.diag(ResultArgToks->getLocation(),
"side effects in the %ordinal0 macro argument %1 are "
"repeated in macro expansion")
<< (ArgNo + 1) << Arg;
Check.diag(MI->getDefinitionLoc(), "macro %0 defined here",
DiagnosticIDs::Note)
<< MacroNameTok.getIdentifierInfo();
}
}
}
unsigned MacroRepeatedPPCallbacks::countArgumentExpansions(
const MacroInfo *MI, const IdentifierInfo *Arg) const {
// Current argument count. When moving forward to a different control-flow
// path this can decrease.
unsigned Current = 0;
// Max argument count.
unsigned Max = 0;
bool SkipParen = false;
int SkipParenCount = 0;
// Has a __builtin_constant_p been found?
bool FoundBuiltin = false;
bool PrevTokenIsHash = false;
// Count when "?" is reached. The "Current" will get this value when the ":"
// is reached.
std::stack<unsigned, SmallVector<unsigned, 8>> CountAtQuestion;
for (const auto &T : MI->tokens()) {
// The result of __builtin_constant_p(x) is 0 if x is a macro argument
// with side effects. If we see a __builtin_constant_p(x) followed by a
// "?" "&&" or "||", then we need to reason about control flow to report
// warnings correctly. Until such reasoning is added, bail out when this
// happens.
if (FoundBuiltin && T.isOneOf(tok::question, tok::ampamp, tok::pipepipe))
return Max;
// Skip stringified tokens.
if (T.is(tok::hash)) {
PrevTokenIsHash = true;
continue;
}
if (PrevTokenIsHash) {
PrevTokenIsHash = false;
continue;
}
// Handling of ? and :.
if (T.is(tok::question)) {
CountAtQuestion.push(Current);
} else if (T.is(tok::colon)) {
if (CountAtQuestion.empty())
return 0;
Current = CountAtQuestion.top();
CountAtQuestion.pop();
}
// If current token is a parenthesis, skip it.
if (SkipParen) {
if (T.is(tok::l_paren))
SkipParenCount++;
else if (T.is(tok::r_paren))
SkipParenCount--;
SkipParen = (SkipParenCount != 0);
if (SkipParen)
continue;
}
IdentifierInfo *TII = T.getIdentifierInfo();
// If not existent, skip it.
if (TII == nullptr)
continue;
// If a __builtin_constant_p is found within the macro definition, don't
// count arguments inside the parentheses and remember that it has been
// seen in case there are "?", "&&" or "||" operators later.
if (TII->getBuiltinID() == Builtin::BI__builtin_constant_p) {
FoundBuiltin = true;
SkipParen = true;
continue;
}
// If another macro is found within the macro definition, skip the macro
// and the eventual arguments.
if (TII->hasMacroDefinition()) {
const MacroInfo *M = PP.getMacroDefinition(TII).getMacroInfo();
if (M != nullptr && M->isFunctionLike())
SkipParen = true;
continue;
}
// Count argument.
if (TII == Arg) {
Current++;
if (Current > Max)
Max = Current;
}
}
return Max;
}
bool MacroRepeatedPPCallbacks::hasSideEffects(
const Token *ResultArgToks) const {
for (; ResultArgToks->isNot(tok::eof); ++ResultArgToks) {
if (ResultArgToks->isOneOf(tok::plusplus, tok::minusminus))
return true;
}
return false;
}
void MacroRepeatedSideEffectsCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
PP->addPPCallbacks(::std::make_unique<MacroRepeatedPPCallbacks>(*this, *PP));
}
} // namespace bugprone
} // namespace tidy
} // namespace clang