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