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 const analyze_scanf::ScanfConversionSpecifier SCS =
80 FS.getConversionSpecifier();
81 if (SCS.isIntArg()) {
82 switch (FS.getLengthModifier().getKind()) {
83 case analyze_scanf::LengthModifier::AsLongLong:
84 CK = ConversionKind::ToLongInt;
85 break;
86 case analyze_scanf::LengthModifier::AsIntMax:
87 CK = ConversionKind::ToIntMax;
88 break;
89 default:
90 CK = ConversionKind::ToInt;
91 break;
92 }
93 } else if (SCS.isUIntArg()) {
94 switch (FS.getLengthModifier().getKind()) {
95 case analyze_scanf::LengthModifier::AsLongLong:
96 CK = ConversionKind::ToLongUInt;
97 break;
98 case analyze_scanf::LengthModifier::AsIntMax:
99 CK = ConversionKind::ToUIntMax;
100 break;
101 default:
102 CK = ConversionKind::ToUInt;
103 break;
104 }
105 } else if (SCS.isDoubleArg()) {
106 switch (FS.getLengthModifier().getKind()) {
107 case analyze_scanf::LengthModifier::AsLongDouble:
108 CK = ConversionKind::ToLongDouble;
109 break;
110 case analyze_scanf::LengthModifier::AsLong:
111 CK = ConversionKind::ToDouble;
112 break;
113 default:
114 CK = ConversionKind::ToFloat;
115 break;
116 }
117 }
118
119 // Continue if we have yet to find a conversion kind that we care about.
120 return CK == ConversionKind::None;
121 }
122
123 public:
124 Handler() = default;
125
126 ConversionKind get() const { return CK; }
127 };
128
129 Handler H;
130 analyze_format_string::ParseScanfString(H, Fmt.begin(), Fmt.end(), LO, TI);
131
132 return H.get();
133}
134
135static StringRef classifyConversionType(ConversionKind K) {
136 switch (K) {
137 case ConversionKind::None:
138 llvm_unreachable("Unexpected conversion kind");
139 case ConversionKind::ToInt:
140 case ConversionKind::ToLongInt:
141 case ConversionKind::ToIntMax:
142 return "an integer value";
143 case ConversionKind::ToUInt:
144 case ConversionKind::ToLongUInt:
145 case ConversionKind::ToUIntMax:
146 return "an unsigned integer value";
147 case ConversionKind::ToFloat:
148 case ConversionKind::ToDouble:
149 case ConversionKind::ToLongDouble:
150 return "a floating-point value";
151 }
152 llvm_unreachable("Unknown conversion kind");
153}
154
155static StringRef classifyReplacement(ConversionKind K) {
156 switch (K) {
157 case ConversionKind::None:
158 llvm_unreachable("Unexpected conversion kind");
159 case ConversionKind::ToInt:
160 return "strtol";
161 case ConversionKind::ToUInt:
162 return "strtoul";
163 case ConversionKind::ToIntMax:
164 return "strtoimax";
165 case ConversionKind::ToLongInt:
166 return "strtoll";
167 case ConversionKind::ToLongUInt:
168 return "strtoull";
169 case ConversionKind::ToUIntMax:
170 return "strtoumax";
171 case ConversionKind::ToFloat:
172 return "strtof";
173 case ConversionKind::ToDouble:
174 return "strtod";
175 case ConversionKind::ToLongDouble:
176 return "strtold";
177 }
178 llvm_unreachable("Unknown conversion kind");
179}
180
182 const MatchFinder::MatchResult &Result) {
183 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("expr");
184 const FunctionDecl *FuncDecl = nullptr;
185 ConversionKind Conversion = ConversionKind::None;
186
187 if (const auto *ConverterFunc =
188 Result.Nodes.getNodeAs<FunctionDecl>("converter")) {
189 // Converter functions are always incorrect to use.
190 FuncDecl = ConverterFunc;
191 Conversion = classifyConversionFunc(ConverterFunc);
192 } else if (const auto *FFD =
193 Result.Nodes.getNodeAs<FunctionDecl>("formatted")) {
194 StringRef FmtStr;
195 // The format string comes from the call expression and depends on which
196 // flavor of scanf is called.
197 // Index 0: scanf, vscanf, Index 1: fscanf, sscanf, vfscanf, vsscanf.
198 const unsigned Idx =
199 (FFD->getName() == "scanf" || FFD->getName() == "vscanf") ? 0 : 1;
200
201 // Given the index, see if the call expression argument at that index is
202 // a string literal.
203 if (Call->getNumArgs() < Idx)
204 return;
205
206 if (const Expr *Arg = Call->getArg(Idx)->IgnoreParenImpCasts()) {
207 if (const auto *SL = dyn_cast<StringLiteral>(Arg)) {
208 FmtStr = SL->getString();
209 }
210 }
211
212 // If we could not get the format string, bail out.
213 if (FmtStr.empty())
214 return;
215
216 // Formatted input functions need further checking of the format string to
217 // determine whether a problematic conversion may be happening.
218 Conversion = classifyFormatString(FmtStr, getLangOpts(),
219 Result.Context->getTargetInfo());
220 if (Conversion != ConversionKind::None)
221 FuncDecl = FFD;
222 }
223
224 if (!FuncDecl)
225 return;
226
227 diag(Call->getExprLoc(),
228 "%0 used to convert a string to %1, but function will not report "
229 "conversion errors; consider using '%2' instead")
230 << FuncDecl << classifyConversionType(Conversion)
231 << classifyReplacement(Conversion);
232}
233
234} // 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[]