xref: /llvm-project/clang-tools-extra/clang-tidy/cert/StrToNumCheck.cpp (revision cbdc3e1bf9da09911ba353bcd20c6709bda43893)
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 
16 using namespace clang::ast_matchers;
17 
18 namespace clang::tidy::cert {
19 
registerMatchers(MatchFinder * Finder)20 void 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 
35 namespace {
36 enum class ConversionKind {
37   None,
38   ToInt,
39   ToUInt,
40   ToLongInt,
41   ToLongUInt,
42   ToIntMax,
43   ToUIntMax,
44   ToFloat,
45   ToDouble,
46   ToLongDouble
47 };
48 
classifyConversionFunc(const FunctionDecl * FD)49 ConversionKind 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 
classifyFormatString(StringRef Fmt,const LangOptions & LO,const TargetInfo & TI)57 ConversionKind 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 
classifyConversionType(ConversionKind K)131 StringRef 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 
classifyReplacement(ConversionKind K)151 StringRef 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 
check(const MatchFinder::MatchResult & Result)178 void 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
231