clang-tools  14.0.0git
SuspiciousMissingCommaCheck.cpp
Go to the documentation of this file.
1 //===--- SuspiciousMissingCommaCheck.cpp - clang-tidy----------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang {
16 namespace tidy {
17 namespace bugprone {
18 
19 namespace {
20 
21 bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx,
22  const StringLiteral *Lit) {
23  // String literals surrounded by parentheses are assumed to be on purpose.
24  // i.e.: const char* Array[] = { ("a" "b" "c"), "d", [...] };
25 
26  TraversalKindScope RAII(*Ctx, TK_AsIs);
27  auto Parents = Ctx->getParents(*Lit);
28  if (Parents.size() == 1 && Parents[0].get<ParenExpr>() != nullptr)
29  return true;
30 
31  // Appropriately indented string literals are assumed to be on purpose.
32  // The following frequent indentation is accepted:
33  // const char* Array[] = {
34  // "first literal"
35  // "indented literal"
36  // "indented literal",
37  // "second literal",
38  // [...]
39  // };
40  const SourceManager &SM = Ctx->getSourceManager();
41  bool IndentedCorrectly = true;
42  SourceLocation FirstToken = Lit->getStrTokenLoc(0);
43  FileID BaseFID = SM.getFileID(FirstToken);
44  unsigned int BaseIndent = SM.getSpellingColumnNumber(FirstToken);
45  unsigned int BaseLine = SM.getSpellingLineNumber(FirstToken);
46  for (unsigned int TokNum = 1; TokNum < Lit->getNumConcatenated(); ++TokNum) {
47  SourceLocation Token = Lit->getStrTokenLoc(TokNum);
48  FileID FID = SM.getFileID(Token);
49  unsigned int Indent = SM.getSpellingColumnNumber(Token);
50  unsigned int Line = SM.getSpellingLineNumber(Token);
51  if (FID != BaseFID || Line != BaseLine + TokNum || Indent <= BaseIndent) {
52  IndentedCorrectly = false;
53  break;
54  }
55  }
56  if (IndentedCorrectly)
57  return true;
58 
59  // There is no pattern recognized by the checker, assume it's not on purpose.
60  return false;
61 }
62 
63 AST_MATCHER_P(StringLiteral, isConcatenatedLiteral, unsigned,
64  MaxConcatenatedTokens) {
65  return Node.getNumConcatenated() > 1 &&
66  Node.getNumConcatenated() < MaxConcatenatedTokens &&
67  !isConcatenatedLiteralsOnPurpose(&Finder->getASTContext(), &Node);
68 }
69 
70 } // namespace
71 
72 SuspiciousMissingCommaCheck::SuspiciousMissingCommaCheck(
73  StringRef Name, ClangTidyContext *Context)
74  : ClangTidyCheck(Name, Context),
75  SizeThreshold(Options.get("SizeThreshold", 5U)),
76  RatioThreshold(std::stod(Options.get("RatioThreshold", ".2"))),
77  MaxConcatenatedTokens(Options.get("MaxConcatenatedTokens", 5U)) {}
78 
81  Options.store(Opts, "SizeThreshold", SizeThreshold);
82  Options.store(Opts, "RatioThreshold", std::to_string(RatioThreshold));
83  Options.store(Opts, "MaxConcatenatedTokens", MaxConcatenatedTokens);
84 }
85 
87  const auto ConcatenatedStringLiteral =
88  stringLiteral(isConcatenatedLiteral(MaxConcatenatedTokens)).bind("str");
89 
90  const auto StringsInitializerList =
91  initListExpr(hasType(constantArrayType()),
92  has(ignoringParenImpCasts(expr(ConcatenatedStringLiteral))));
93 
94  Finder->addMatcher(StringsInitializerList.bind("list"), this);
95 }
96 
98  const MatchFinder::MatchResult &Result) {
99  const auto *InitializerList = Result.Nodes.getNodeAs<InitListExpr>("list");
100  const auto *ConcatenatedLiteral =
101  Result.Nodes.getNodeAs<StringLiteral>("str");
102  assert(InitializerList && ConcatenatedLiteral);
103 
104  // Skip small arrays as they often generate false-positive.
105  unsigned int Size = InitializerList->getNumInits();
106  if (Size < SizeThreshold)
107  return;
108 
109  // Count the number of occurrence of concatenated string literal.
110  unsigned int Count = 0;
111  for (unsigned int I = 0; I < Size; ++I) {
112  const Expr *Child = InitializerList->getInit(I)->IgnoreImpCasts();
113  if (const auto *Literal = dyn_cast<StringLiteral>(Child)) {
114  if (Literal->getNumConcatenated() > 1)
115  ++Count;
116  }
117  }
118 
119  // Warn only when concatenation is not common in this initializer list.
120  // The current threshold is set to less than 1/5 of the string literals.
121  if (double(Count) / Size > RatioThreshold)
122  return;
123 
124  diag(ConcatenatedLiteral->getBeginLoc(),
125  "suspicious string literal, probably missing a comma");
126 }
127 
128 } // namespace bugprone
129 } // namespace tidy
130 } // namespace clang
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::clangd::TypeHierarchyDirection::Parents
@ Parents
Ctx
Context Ctx
Definition: TUScheduler.cpp:460
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::bugprone::SuspiciousMissingCommaCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: SuspiciousMissingCommaCheck.cpp:97
Line
int Line
Definition: PreprocessorTracker.cpp:514
SuspiciousMissingCommaCheck.h
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:416
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:71
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
clang::tidy::bugprone::SuspiciousMissingCommaCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: SuspiciousMissingCommaCheck.cpp:86
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::bugprone::SuspiciousMissingCommaCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: SuspiciousMissingCommaCheck.cpp:79
clang::tidy::ClangTidyCheck::OptionsView::store
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: ClangTidyCheck.cpp:120
clang::tidy::bugprone::AST_MATCHER_P
AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N)
Matches functions that have at least the specified amount of parameters.
Definition: EasilySwappableParametersCheck.cpp:1877