clang-tools  3.9.0
SuspiciousMissingCommaCheck.cpp
Go to the documentation of this file.
1 //===--- SuspiciousMissingCommaCheck.cpp - clang-tidy----------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang {
17 namespace tidy {
18 namespace misc {
19 
20 namespace {
21 
22 bool isConcatenatedLiteralsOnPurpose(ASTContext* Ctx,
23  const StringLiteral* Lit) {
24  // String literals surrounded by parentheses are assumed to be on purpose.
25  // i.e.: const char* Array[] = { ("a" "b" "c"), "d", [...] };
26  auto Parents = Ctx->getParents(*Lit);
27  if (Parents.size() == 1 && Parents[0].get<ParenExpr>() != nullptr)
28  return true;
29 
30  // Appropriately indented string literals are assumed to be on purpose.
31  // The following frequent indentation is accepted:
32  // const char* Array[] = {
33  // "first literal"
34  // "indented literal"
35  // "indented literal",
36  // "second literal",
37  // [...]
38  // };
39  const SourceManager& SM = Ctx->getSourceManager();
40  bool IndentedCorrectly = true;
41  SourceLocation FirstToken = Lit->getStrTokenLoc(0);
42  FileID BaseFID = SM.getFileID(FirstToken);
43  unsigned int BaseIndent = SM.getSpellingColumnNumber(FirstToken);
44  unsigned int BaseLine = SM.getSpellingLineNumber(FirstToken);
45  for (unsigned int TokNum = 1; TokNum < Lit->getNumConcatenated(); ++ TokNum) {
46  SourceLocation Token = Lit->getStrTokenLoc(TokNum);
47  FileID FID = SM.getFileID(Token);
48  unsigned int Indent = SM.getSpellingColumnNumber(Token);
49  unsigned int Line = SM.getSpellingLineNumber(Token);
50  if (FID != BaseFID || Line != BaseLine + TokNum || Indent <= BaseIndent) {
51  IndentedCorrectly = false;
52  break;
53  }
54  }
55  if (IndentedCorrectly)
56  return true;
57 
58  // There is no pattern recognized by the checker, assume it's not on purpose.
59  return false;
60 }
61 
62 AST_MATCHER_P(StringLiteral, isConcatenatedLiteral,
63  unsigned, MaxConcatenatedTokens) {
64  return Node.getNumConcatenated() > 1 &&
65  Node.getNumConcatenated() < MaxConcatenatedTokens &&
66  !isConcatenatedLiteralsOnPurpose(&Finder->getASTContext(), &Node);
67 }
68 
69 } // namespace
70 
71 SuspiciousMissingCommaCheck::SuspiciousMissingCommaCheck(
72  StringRef Name, ClangTidyContext *Context)
73  : ClangTidyCheck(Name, Context),
74  SizeThreshold(Options.get("SizeThreshold", 5U)),
75  RatioThreshold(std::stod(Options.get("RatioThreshold", ".2"))),
76  MaxConcatenatedTokens(Options.get("MaxConcatenatedTokens", 5U)) {}
77 
80  Options.store(Opts, "SizeThreshold", SizeThreshold);
81  Options.store(Opts, "RatioThreshold", std::to_string(RatioThreshold));
82  Options.store(Opts, "MaxConcatenatedTokens", MaxConcatenatedTokens);
83 }
84 
86  const auto ConcatenatedStringLiteral =
87  stringLiteral(isConcatenatedLiteral(MaxConcatenatedTokens)).bind("str");
88 
89  const auto StringsInitializerList =
90  initListExpr(hasType(constantArrayType()),
91  has(ignoringParenImpCasts(expr(ConcatenatedStringLiteral))));
92 
93  Finder->addMatcher(StringsInitializerList.bind("list"), this);
94 }
95 
97  const MatchFinder::MatchResult &Result) {
98  const auto *InitializerList = Result.Nodes.getNodeAs<InitListExpr>("list");
99  const auto *ConcatenatedLiteral =
100  Result.Nodes.getNodeAs<StringLiteral>("str");
101  assert(InitializerList && ConcatenatedLiteral);
102 
103  // Skip small arrays as they often generate false-positive.
104  unsigned int Size = InitializerList->getNumInits();
105  if (Size < SizeThreshold) return;
106 
107  // Count the number of occurence of concatenated string literal.
108  unsigned int Count = 0;
109  for (unsigned int i = 0; i < Size; ++i) {
110  const Expr *Child = InitializerList->getInit(i)->IgnoreImpCasts();
111  if (const auto *Literal = dyn_cast<StringLiteral>(Child)) {
112  if (Literal->getNumConcatenated() > 1) ++Count;
113  }
114  }
115 
116  // Warn only when concatenation is not common in this initializer list.
117  // The current threshold is set to less than 1/5 of the string literals.
118  if (double(Count) / Size > RatioThreshold) return;
119 
120  diag(ConcatenatedLiteral->getLocStart(),
121  "suspicious string literal, probably missing a comma");
122 }
123 
124 } // namespace misc
125 } // namespace tidy
126 } // namespace clang
const std::string Name
Definition: USRFinder.cpp:140
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:210
AST_MATCHER_P(CXXForRangeStmt, hasRangeBeginEndStmt, ast_matchers::internal::Matcher< DeclStmt >, InnerMatcher)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Base class for all clang-tidy checks.
Definition: ClangTidy.h:110
SourceManager & SM
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidy.cpp:385
std::map< std::string, std::string > OptionMap
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
ClangTidyContext & Context
Definition: ClangTidy.cpp:93
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidy.cpp:352
const NamedDecl * Result
Definition: USRFinder.cpp:137