forked from OSchip/llvm-project
107 lines
3.5 KiB
C++
107 lines
3.5 KiB
C++
//===--- MultipleStatementMacroCheck.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 "MultipleStatementMacroCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace bugprone {
|
|
|
|
namespace {
|
|
|
|
AST_MATCHER(Expr, isInMacro) { return Node.getBeginLoc().isMacroID(); }
|
|
|
|
/// Find the next statement after `S`.
|
|
const Stmt *nextStmt(const MatchFinder::MatchResult &Result, const Stmt *S) {
|
|
auto Parents = Result.Context->getParents(*S);
|
|
if (Parents.empty())
|
|
return nullptr;
|
|
const auto *Parent = Parents[0].get<Stmt>();
|
|
if (!Parent)
|
|
return nullptr;
|
|
const Stmt *Prev = nullptr;
|
|
for (const Stmt *Child : Parent->children()) {
|
|
if (Prev == S)
|
|
return Child;
|
|
Prev = Child;
|
|
}
|
|
return nextStmt(Result, Parent);
|
|
}
|
|
|
|
using ExpansionRanges = std::vector<SourceRange>;
|
|
|
|
/// \brief Get all the macro expansion ranges related to `Loc`.
|
|
///
|
|
/// The result is ordered from most inner to most outer.
|
|
ExpansionRanges getExpansionRanges(SourceLocation Loc,
|
|
const MatchFinder::MatchResult &Result) {
|
|
ExpansionRanges Locs;
|
|
while (Loc.isMacroID()) {
|
|
Locs.push_back(
|
|
Result.SourceManager->getImmediateExpansionRange(Loc).getAsRange());
|
|
Loc = Locs.back().getBegin();
|
|
}
|
|
return Locs;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void MultipleStatementMacroCheck::registerMatchers(MatchFinder *Finder) {
|
|
const auto Inner = expr(isInMacro(), unless(compoundStmt())).bind("inner");
|
|
Finder->addMatcher(
|
|
stmt(anyOf(ifStmt(hasThen(Inner)), ifStmt(hasElse(Inner)).bind("else"),
|
|
whileStmt(hasBody(Inner)), forStmt(hasBody(Inner))))
|
|
.bind("outer"),
|
|
this);
|
|
}
|
|
|
|
void MultipleStatementMacroCheck::check(
|
|
const MatchFinder::MatchResult &Result) {
|
|
const auto *Inner = Result.Nodes.getNodeAs<Expr>("inner");
|
|
const auto *Outer = Result.Nodes.getNodeAs<Stmt>("outer");
|
|
const auto *Next = nextStmt(Result, Outer);
|
|
if (!Next)
|
|
return;
|
|
|
|
SourceLocation OuterLoc = Outer->getBeginLoc();
|
|
if (Result.Nodes.getNodeAs<Stmt>("else"))
|
|
OuterLoc = cast<IfStmt>(Outer)->getElseLoc();
|
|
|
|
auto InnerRanges = getExpansionRanges(Inner->getBeginLoc(), Result);
|
|
auto OuterRanges = getExpansionRanges(OuterLoc, Result);
|
|
auto NextRanges = getExpansionRanges(Next->getBeginLoc(), Result);
|
|
|
|
// Remove all the common ranges, starting from the top (the last ones in the
|
|
// list).
|
|
while (!InnerRanges.empty() && !OuterRanges.empty() && !NextRanges.empty() &&
|
|
InnerRanges.back() == OuterRanges.back() &&
|
|
InnerRanges.back() == NextRanges.back()) {
|
|
InnerRanges.pop_back();
|
|
OuterRanges.pop_back();
|
|
NextRanges.pop_back();
|
|
}
|
|
|
|
// Inner and Next must have at least one more macro that Outer doesn't have,
|
|
// and that range must be common to both.
|
|
if (InnerRanges.empty() || NextRanges.empty() ||
|
|
InnerRanges.back() != NextRanges.back())
|
|
return;
|
|
|
|
diag(InnerRanges.back().getBegin(), "multiple statement macro used without "
|
|
"braces; some statements will be "
|
|
"unconditionally executed");
|
|
}
|
|
|
|
} // namespace bugprone
|
|
} // namespace tidy
|
|
} // namespace clang
|