127 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "SuspiciousMissingCommaCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12 13using namespace clang::ast_matchers;14 15namespace clang::tidy::bugprone {16 17static bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx,18 const StringLiteral *Lit) {19 // String literals surrounded by parentheses are assumed to be on purpose.20 // i.e.: const char* Array[] = { ("a" "b" "c"), "d", [...] };21 22 const TraversalKindScope RAII(*Ctx, TK_AsIs);23 auto Parents = Ctx->getParents(*Lit);24 if (Parents.size() == 1 && Parents[0].get<ParenExpr>() != nullptr)25 return true;26 27 // Appropriately indented string literals are assumed to be on purpose.28 // The following frequent indentation is accepted:29 // const char* Array[] = {30 // "first literal"31 // "indented literal"32 // "indented literal",33 // "second literal",34 // [...]35 // };36 const SourceManager &SM = Ctx->getSourceManager();37 bool IndentedCorrectly = true;38 const SourceLocation FirstToken = Lit->getStrTokenLoc(0);39 const FileID BaseFID = SM.getFileID(FirstToken);40 const unsigned int BaseIndent = SM.getSpellingColumnNumber(FirstToken);41 const unsigned int BaseLine = SM.getSpellingLineNumber(FirstToken);42 for (unsigned int TokNum = 1; TokNum < Lit->getNumConcatenated(); ++TokNum) {43 const SourceLocation Token = Lit->getStrTokenLoc(TokNum);44 const FileID FID = SM.getFileID(Token);45 const unsigned int Indent = SM.getSpellingColumnNumber(Token);46 const unsigned int Line = SM.getSpellingLineNumber(Token);47 if (FID != BaseFID || Line != BaseLine + TokNum || Indent <= BaseIndent) {48 IndentedCorrectly = false;49 break;50 }51 }52 if (IndentedCorrectly)53 return true;54 55 // There is no pattern recognized by the checker, assume it's not on purpose.56 return false;57}58 59namespace {60 61AST_MATCHER_P(StringLiteral, isConcatenatedLiteral, unsigned,62 MaxConcatenatedTokens) {63 return Node.getNumConcatenated() > 1 &&64 Node.getNumConcatenated() < MaxConcatenatedTokens &&65 !isConcatenatedLiteralsOnPurpose(&Finder->getASTContext(), &Node);66}67 68} // namespace69 70SuspiciousMissingCommaCheck::SuspiciousMissingCommaCheck(71 StringRef Name, ClangTidyContext *Context)72 : ClangTidyCheck(Name, Context),73 SizeThreshold(Options.get("SizeThreshold", 5U)),74 RatioThreshold(std::stod(Options.get("RatioThreshold", ".2").str())),75 MaxConcatenatedTokens(Options.get("MaxConcatenatedTokens", 5U)) {}76 77void SuspiciousMissingCommaCheck::storeOptions(78 ClangTidyOptions::OptionMap &Opts) {79 Options.store(Opts, "SizeThreshold", SizeThreshold);80 Options.store(Opts, "RatioThreshold", std::to_string(RatioThreshold));81 Options.store(Opts, "MaxConcatenatedTokens", MaxConcatenatedTokens);82}83 84void SuspiciousMissingCommaCheck::registerMatchers(MatchFinder *Finder) {85 const auto ConcatenatedStringLiteral =86 stringLiteral(isConcatenatedLiteral(MaxConcatenatedTokens)).bind("str");87 88 const auto StringsInitializerList =89 initListExpr(hasType(constantArrayType()),90 has(ignoringParenImpCasts(expr(ConcatenatedStringLiteral))));91 92 Finder->addMatcher(StringsInitializerList.bind("list"), this);93}94 95void SuspiciousMissingCommaCheck::check(96 const MatchFinder::MatchResult &Result) {97 const auto *InitializerList = Result.Nodes.getNodeAs<InitListExpr>("list");98 const auto *ConcatenatedLiteral =99 Result.Nodes.getNodeAs<StringLiteral>("str");100 assert(InitializerList && ConcatenatedLiteral);101 102 // Skip small arrays as they often generate false-positive.103 const unsigned int Size = InitializerList->getNumInits();104 if (Size < SizeThreshold)105 return;106 107 // Count the number of occurrence 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)113 ++Count;114 }115 }116 117 // Warn only when concatenation is not common in this initializer list.118 // The current threshold is set to less than 1/5 of the string literals.119 if (double(Count) / Size > RatioThreshold)120 return;121 122 diag(ConcatenatedLiteral->getBeginLoc(),123 "suspicious string literal, probably missing a comma");124}125 126} // namespace clang::tidy::bugprone127