summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp')
-rw-r--r--clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp129
1 files changed, 129 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp
new file mode 100644
index 00000000000..3831dc3eaee
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp
@@ -0,0 +1,129 @@
+//===--- 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
OpenPOWER on IntegriCloud