clang-tools 20.0.0git
StrToNumCheck.cpp
Go to the documentation of this file.
1//===-- StrToNumCheck.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
9#include "StrToNumCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/FormatString.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "llvm/ADT/StringSwitch.h"
14#include <cassert>
15
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::cert {
19
20void StrToNumCheck::registerMatchers(MatchFinder *Finder) {
21 // Match any function call to the C standard library string conversion
22 // functions that do no error checking.
23 Finder->addMatcher(
24 callExpr(
25 callee(functionDecl(anyOf(
26 functionDecl(hasAnyName("::atoi", "::atof", "::atol", "::atoll"))
27 .bind("converter"),
28 functionDecl(hasAnyName("::scanf", "::sscanf", "::fscanf",
29 "::vfscanf", "::vscanf", "::vsscanf"))
30 .bind("formatted")))))
31 .bind("expr"),
32 this);
33}
34
35namespace {
36enum class ConversionKind {
37 None,
38 ToInt,
39 ToUInt,
40 ToLongInt,
41 ToLongUInt,
42 ToIntMax,
43 ToUIntMax,
44 ToFloat,
45 ToDouble,
46 ToLongDouble
47};
48
49ConversionKind classifyConversionFunc(const FunctionDecl *FD) {
50 return llvm::StringSwitch<ConversionKind>(FD->getName())
51 .Cases("atoi", "atol", ConversionKind::ToInt)
52 .Case("atoll", ConversionKind::ToLongInt)
53 .Case("atof", ConversionKind::ToDouble)
54 .Default(ConversionKind::None);
55}
56
57ConversionKind classifyFormatString(StringRef Fmt, const LangOptions &LO,
58 const TargetInfo &TI) {
59 // Scan the format string for the first problematic format specifier, then
60 // report that as the conversion type. This will miss additional conversion
61 // specifiers, but that is acceptable behavior.
62
63 class Handler : public analyze_format_string::FormatStringHandler {
64 ConversionKind CK = ConversionKind::None;
65
66 bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS,
67 const char *StartSpecifier,
68 unsigned SpecifierLen) override {
69 // If we just consume the argument without assignment, we don't care
70 // about it having conversion errors.
71 if (!FS.consumesDataArgument())
72 return true;
73
74 // Get the conversion specifier and use it to determine the conversion
75 // kind.
76 analyze_scanf::ScanfConversionSpecifier SCS = FS.getConversionSpecifier();
77 if (SCS.isIntArg()) {
78 switch (FS.getLengthModifier().getKind()) {
79 case analyze_scanf::LengthModifier::AsLongLong:
80 CK = ConversionKind::ToLongInt;
81 break;
82 case analyze_scanf::LengthModifier::AsIntMax:
83 CK = ConversionKind::ToIntMax;
84 break;
85 default:
86 CK = ConversionKind::ToInt;
87 break;
88 }
89 } else if (SCS.isUIntArg()) {
90 switch (FS.getLengthModifier().getKind()) {
91 case analyze_scanf::LengthModifier::AsLongLong:
92 CK = ConversionKind::ToLongUInt;
93 break;
94 case analyze_scanf::LengthModifier::AsIntMax:
95 CK = ConversionKind::ToUIntMax;
96 break;
97 default:
98 CK = ConversionKind::ToUInt;
99 break;
100 }
101 } else if (SCS.isDoubleArg()) {
102 switch (FS.getLengthModifier().getKind()) {
103 case analyze_scanf::LengthModifier::AsLongDouble:
104 CK = ConversionKind::ToLongDouble;
105 break;
106 case analyze_scanf::LengthModifier::AsLong:
107 CK = ConversionKind::ToDouble;
108 break;
109 default:
110 CK = ConversionKind::ToFloat;
111 break;
112 }
113 }
114
115 // Continue if we have yet to find a conversion kind that we care about.
116 return CK == ConversionKind::None;
117 }
118
119 public:
120 Handler() = default;
121
122 ConversionKind get() const { return CK; }
123 };
124
125 Handler H;
126 analyze_format_string::ParseScanfString(H, Fmt.begin(), Fmt.end(), LO, TI);
127
128 return H.get();
129}
130
131StringRef classifyConversionType(ConversionKind K) {
132 switch (K) {
133 case ConversionKind::None:
134 llvm_unreachable("Unexpected conversion kind");
135 case ConversionKind::ToInt:
136 case ConversionKind::ToLongInt:
137 case ConversionKind::ToIntMax:
138 return "an integer value";
139 case ConversionKind::ToUInt:
140 case ConversionKind::ToLongUInt:
141 case ConversionKind::ToUIntMax:
142 return "an unsigned integer value";
143 case ConversionKind::ToFloat:
144 case ConversionKind::ToDouble:
145 case ConversionKind::ToLongDouble:
146 return "a floating-point value";
147 }
148 llvm_unreachable("Unknown conversion kind");
149}
150
151StringRef classifyReplacement(ConversionKind K) {
152 switch (K) {
153 case ConversionKind::None:
154 llvm_unreachable("Unexpected conversion kind");
155 case ConversionKind::ToInt:
156 return "strtol";
157 case ConversionKind::ToUInt:
158 return "strtoul";
159 case ConversionKind::ToIntMax:
160 return "strtoimax";
161 case ConversionKind::ToLongInt:
162 return "strtoll";
163 case ConversionKind::ToLongUInt:
164 return "strtoull";
165 case ConversionKind::ToUIntMax:
166 return "strtoumax";
167 case ConversionKind::ToFloat:
168 return "strtof";
169 case ConversionKind::ToDouble:
170 return "strtod";
171 case ConversionKind::ToLongDouble:
172 return "strtold";
173 }
174 llvm_unreachable("Unknown conversion kind");
175}
176} // unnamed namespace
177
178void StrToNumCheck::check(const MatchFinder::MatchResult &Result) {
179 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("expr");
180 const FunctionDecl *FuncDecl = nullptr;
181 ConversionKind Conversion = ConversionKind::None;
182
183 if (const auto *ConverterFunc =
184 Result.Nodes.getNodeAs<FunctionDecl>("converter")) {
185 // Converter functions are always incorrect to use.
186 FuncDecl = ConverterFunc;
187 Conversion = classifyConversionFunc(ConverterFunc);
188 } else if (const auto *FFD =
189 Result.Nodes.getNodeAs<FunctionDecl>("formatted")) {
190 StringRef FmtStr;
191 // The format string comes from the call expression and depends on which
192 // flavor of scanf is called.
193 // Index 0: scanf, vscanf, Index 1: fscanf, sscanf, vfscanf, vsscanf.
194 unsigned Idx =
195 (FFD->getName() == "scanf" || FFD->getName() == "vscanf") ? 0 : 1;
196
197 // Given the index, see if the call expression argument at that index is
198 // a string literal.
199 if (Call->getNumArgs() < Idx)
200 return;
201
202 if (const Expr *Arg = Call->getArg(Idx)->IgnoreParenImpCasts()) {
203 if (const auto *SL = dyn_cast<StringLiteral>(Arg)) {
204 FmtStr = SL->getString();
205 }
206 }
207
208 // If we could not get the format string, bail out.
209 if (FmtStr.empty())
210 return;
211
212 // Formatted input functions need further checking of the format string to
213 // determine whether a problematic conversion may be happening.
214 Conversion = classifyFormatString(FmtStr, getLangOpts(),
215 Result.Context->getTargetInfo());
216 if (Conversion != ConversionKind::None)
217 FuncDecl = FFD;
218 }
219
220 if (!FuncDecl)
221 return;
222
223 diag(Call->getExprLoc(),
224 "%0 used to convert a string to %1, but function will not report "
225 "conversion errors; consider using '%2' instead")
226 << FuncDecl << classifyConversionType(Conversion)
227 << classifyReplacement(Conversion);
228}
229
230} // namespace clang::tidy::cert
static constexpr Index None
Definition: Bracket.cpp:73
Kind K
Definition: Rename.cpp:474
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static constexpr const char FuncDecl[]