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