blob: 09409d87020c8fef910fb3223e62fc8a0954f329 [file] [log] [blame]
//===--- SuspiciousMissingCommaCheck.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 "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->getBeginLoc(),
"suspicious string literal, probably missing a comma");
}
} // namespace bugprone
} // namespace tidy
} // namespace clang