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