forked from OSchip/llvm-project
130 lines
4.6 KiB
C++
130 lines
4.6 KiB
C++
|
//===--- SuspiciousMissingCommaCheck.cpp - clang-tidy----------------------===//
|
||
|
//
|
||
|
// The LLVM Compiler Infrastructure
|
||
|
//
|
||
|
// This file is distributed under the University of Illinois Open Source
|
||
|
// License. See LICENSE.TXT for details.
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "SuspiciousMissingCommaCheck.h"
|
||
|
#include "clang/AST/ASTContext.h"
|
||
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||
|
|
||
|
using namespace clang::ast_matchers;
|
||
|
|
||
|
namespace clang {
|
||
|
namespace tidy {
|
||
|
namespace bugprone {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx,
|
||
|
const StringLiteral *Lit) {
|
||
|
// String literals surrounded by parentheses are assumed to be on purpose.
|
||
|
// i.e.: const char* Array[] = { ("a" "b" "c"), "d", [...] };
|
||
|
auto Parents = Ctx->getParents(*Lit);
|
||
|
if (Parents.size() == 1 && Parents[0].get<ParenExpr>() != nullptr)
|
||
|
return true;
|
||
|
|
||
|
// Appropriately indented string literals are assumed to be on purpose.
|
||
|
// The following frequent indentation is accepted:
|
||
|
// const char* Array[] = {
|
||
|
// "first literal"
|
||
|
// "indented literal"
|
||
|
// "indented literal",
|
||
|
// "second literal",
|
||
|
// [...]
|
||
|
// };
|
||
|
const SourceManager &SM = Ctx->getSourceManager();
|
||
|
bool IndentedCorrectly = true;
|
||
|
SourceLocation FirstToken = Lit->getStrTokenLoc(0);
|
||
|
FileID BaseFID = SM.getFileID(FirstToken);
|
||
|
unsigned int BaseIndent = SM.getSpellingColumnNumber(FirstToken);
|
||
|
unsigned int BaseLine = SM.getSpellingLineNumber(FirstToken);
|
||
|
for (unsigned int TokNum = 1; TokNum < Lit->getNumConcatenated(); ++TokNum) {
|
||
|
SourceLocation Token = Lit->getStrTokenLoc(TokNum);
|
||
|
FileID FID = SM.getFileID(Token);
|
||
|
unsigned int Indent = SM.getSpellingColumnNumber(Token);
|
||
|
unsigned int Line = SM.getSpellingLineNumber(Token);
|
||
|
if (FID != BaseFID || Line != BaseLine + TokNum || Indent <= BaseIndent) {
|
||
|
IndentedCorrectly = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (IndentedCorrectly)
|
||
|
return true;
|
||
|
|
||
|
// There is no pattern recognized by the checker, assume it's not on purpose.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
AST_MATCHER_P(StringLiteral, isConcatenatedLiteral, unsigned,
|
||
|
MaxConcatenatedTokens) {
|
||
|
return Node.getNumConcatenated() > 1 &&
|
||
|
Node.getNumConcatenated() < MaxConcatenatedTokens &&
|
||
|
!isConcatenatedLiteralsOnPurpose(&Finder->getASTContext(), &Node);
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
SuspiciousMissingCommaCheck::SuspiciousMissingCommaCheck(
|
||
|
StringRef Name, ClangTidyContext *Context)
|
||
|
: ClangTidyCheck(Name, Context),
|
||
|
SizeThreshold(Options.get("SizeThreshold", 5U)),
|
||
|
RatioThreshold(std::stod(Options.get("RatioThreshold", ".2"))),
|
||
|
MaxConcatenatedTokens(Options.get("MaxConcatenatedTokens", 5U)) {}
|
||
|
|
||
|
void SuspiciousMissingCommaCheck::storeOptions(
|
||
|
ClangTidyOptions::OptionMap &Opts) {
|
||
|
Options.store(Opts, "SizeThreshold", SizeThreshold);
|
||
|
Options.store(Opts, "RatioThreshold", std::to_string(RatioThreshold));
|
||
|
Options.store(Opts, "MaxConcatenatedTokens", MaxConcatenatedTokens);
|
||
|
}
|
||
|
|
||
|
void SuspiciousMissingCommaCheck::registerMatchers(MatchFinder *Finder) {
|
||
|
const auto ConcatenatedStringLiteral =
|
||
|
stringLiteral(isConcatenatedLiteral(MaxConcatenatedTokens)).bind("str");
|
||
|
|
||
|
const auto StringsInitializerList =
|
||
|
initListExpr(hasType(constantArrayType()),
|
||
|
has(ignoringParenImpCasts(expr(ConcatenatedStringLiteral))));
|
||
|
|
||
|
Finder->addMatcher(StringsInitializerList.bind("list"), this);
|
||
|
}
|
||
|
|
||
|
void SuspiciousMissingCommaCheck::check(
|
||
|
const MatchFinder::MatchResult &Result) {
|
||
|
const auto *InitializerList = Result.Nodes.getNodeAs<InitListExpr>("list");
|
||
|
const auto *ConcatenatedLiteral =
|
||
|
Result.Nodes.getNodeAs<StringLiteral>("str");
|
||
|
assert(InitializerList && ConcatenatedLiteral);
|
||
|
|
||
|
// Skip small arrays as they often generate false-positive.
|
||
|
unsigned int Size = InitializerList->getNumInits();
|
||
|
if (Size < SizeThreshold)
|
||
|
return;
|
||
|
|
||
|
// Count the number of occurence of concatenated string literal.
|
||
|
unsigned int Count = 0;
|
||
|
for (unsigned int i = 0; i < Size; ++i) {
|
||
|
const Expr *Child = InitializerList->getInit(i)->IgnoreImpCasts();
|
||
|
if (const auto *Literal = dyn_cast<StringLiteral>(Child)) {
|
||
|
if (Literal->getNumConcatenated() > 1)
|
||
|
++Count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Warn only when concatenation is not common in this initializer list.
|
||
|
// The current threshold is set to less than 1/5 of the string literals.
|
||
|
if (double(Count) / Size > RatioThreshold)
|
||
|
return;
|
||
|
|
||
|
diag(ConcatenatedLiteral->getLocStart(),
|
||
|
"suspicious string literal, probably missing a comma");
|
||
|
}
|
||
|
|
||
|
} // namespace bugprone
|
||
|
} // namespace tidy
|
||
|
} // namespace clang
|