clang-tools 23.0.0git
FormatvStringCheck.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
11#include "clang/AST/DeclTemplate.h"
12#include "clang/AST/Expr.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "llvm/ADT/SmallBitVector.h"
15#include "llvm/ADT/SmallVector.h"
16#include "llvm/Support/Error.h"
17
18using namespace clang::ast_matchers;
19
21
22namespace {
23
24struct ParseResult {
26 unsigned MaxIndex = 0;
27};
28
29} // namespace
30
31static Expected<ParseResult> parseFormatvString(StringRef Fmt) {
32 ParseResult Result;
33 unsigned NextAutoIndex = 0;
34 bool HasAutomatic = false;
35 bool HasExplicit = false;
36
37 while (!Fmt.empty()) {
38 const size_t OpenBrace = Fmt.find('{');
39 if (OpenBrace == StringRef::npos)
40 break;
41
42 Fmt = Fmt.drop_front(OpenBrace);
43
44 // Handle escaped braces '{{'.
45 if (Fmt.consume_front("{{"))
46 continue;
47
48 // Find the closing '}'.
49 const size_t CloseBrace = Fmt.find('}');
50 if (CloseBrace == StringRef::npos)
51 return llvm::createStringError("unterminated brace in format string");
52
53 // Extract the content between braces.
54 const StringRef Content = Fmt.substr(1, CloseBrace - 1);
55 Fmt = Fmt.drop_front(CloseBrace + 1);
56
57 // Parse the replacement field: [index] ["," layout] [":" format]
58 StringRef IndexStr = Content.substr(0, Content.find_first_of(",:"));
59
60 IndexStr = IndexStr.trim();
61
62 unsigned Index = 0;
63 if (IndexStr.empty()) {
64 Index = NextAutoIndex++;
65 HasAutomatic = true;
66 } else {
67 if (IndexStr.getAsInteger(10, Index))
68 return llvm::createStringError(
69 "invalid replacement index in format string");
70 HasExplicit = true;
71 }
72
73 Result.Indices.push_back(Index);
74 Result.MaxIndex = std::max(Result.MaxIndex, Index);
75 }
76
77 if (HasAutomatic && HasExplicit)
78 return llvm::createStringError(
79 "format string mixes automatic and explicit indices");
80
81 return Result;
82}
83
85 ClangTidyContext *Context)
86 : ClangTidyCheck(Name, Context),
87 AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
88 Functions = utils::options::parseStringList(AdditionalFunctions);
89 Functions.emplace_back("::llvm::formatv");
90 Functions.emplace_back("::llvm::createStringErrorV");
91}
92
94 Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
95}
96
97void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
98 // Build a matcher for all configured function names.
99 Finder->addMatcher(
100 callExpr(callee(functionDecl(hasAnyName(Functions),
101 ast_matchers::isTemplateInstantiation())),
102 argumentCountAtLeast(1))
103 .bind("call"),
104 this);
105}
106
107void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
108 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
109 assert(Call && Call->getNumArgs() > 0);
110
111 const auto *FD = Call->getDirectCallee();
112 assert(FD);
113
114 // Find the format string index from the template signature: it's the
115 // parameter immediately before the trailing parameter pack.
116 const FunctionDecl *TemplateDecl = FD;
117 if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
118 TemplateDecl = Primary->getTemplatedDecl();
119
120 const unsigned NumDeclParams = TemplateDecl->getNumParams();
121 if (NumDeclParams < 2)
122 return;
123
124 const unsigned PackParamIndex = NumDeclParams - 1;
125 if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
126 return;
127
128 const unsigned FmtStringIndex = PackParamIndex - 1;
129
130 if (Call->getNumArgs() <= FmtStringIndex)
131 return;
132
133 // Extract the format string literal.
134 const Expr *FmtArg = Call->getArg(FmtStringIndex)->IgnoreParenImpCasts();
135 const auto *FmtLiteral = dyn_cast<StringLiteral>(FmtArg);
136 if (!FmtLiteral)
137 return;
138
139 const StringRef FmtString = FmtLiteral->getString();
140 const int NumFmtArgs = Call->getNumArgs() - PackParamIndex;
141
142 auto ParsedOrErr = parseFormatvString(FmtString);
143 if (!ParsedOrErr) {
144 diag(FmtLiteral->getBeginLoc(), toString(ParsedOrErr.takeError()));
145 return;
146 }
147
148 const ParseResult &Parsed = *ParsedOrErr;
149 const int NumRequiredArgs = Parsed.Indices.empty() ? 0 : Parsed.MaxIndex + 1;
150
151 if (NumRequiredArgs > NumFmtArgs) {
152 diag(FmtLiteral->getBeginLoc(),
153 "format string requires %0 argument%s0, but %1 argument%s1 "
154 "%plural{1:was|:were}1 provided")
155 << NumRequiredArgs << NumFmtArgs;
156 return;
157 }
158
159 // Check for unused arguments: both indices not referenced by the format
160 // string, and trailing arguments beyond what the format string requires.
161 llvm::SmallBitVector UnusedIndices(NumFmtArgs, true);
162 for (const unsigned Index : Parsed.Indices)
163 UnusedIndices.reset(Index);
164
165 for (const auto UnusedIndex : UnusedIndices.set_bits()) {
166 const Expr *UnusedArg = Call->getArg(PackParamIndex + UnusedIndex);
167 diag(UnusedArg->getBeginLoc(), "argument unused in format string");
168 }
169}
170
171} // namespace clang::tidy::llvm_check
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
FormatvStringCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
static Expected< ParseResult > parseFormatvString(StringRef Fmt)
std::vector< StringRef > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
llvm::StringMap< ClangTidyValue > OptionMap