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 | |
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 | NodeMatch: callExpr( |
25 | callee(InnerMatcher: functionDecl(anyOf( |
26 | functionDecl(hasAnyName("::atoi" , "::atof" , "::atol" , "::atoll" )) |
27 | .bind(ID: "converter" ), |
28 | functionDecl(hasAnyName("::scanf" , "::sscanf" , "::fscanf" , |
29 | "::vfscanf" , "::vscanf" , "::vsscanf" )) |
30 | .bind(ID: "formatted" ))))) |
31 | .bind(ID: "expr" ), |
32 | Action: 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 | |
49 | ConversionKind classifyConversionFunc(const FunctionDecl *FD) { |
50 | return llvm::StringSwitch<ConversionKind>(FD->getName()) |
51 | .Cases(S0: "atoi" , S1: "atol" , Value: ConversionKind::ToInt) |
52 | .Case(S: "atoll" , Value: ConversionKind::ToLongInt) |
53 | .Case(S: "atof" , Value: ConversionKind::ToDouble) |
54 | .Default(Value: ConversionKind::None); |
55 | } |
56 | |
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, beg: Fmt.begin(), end: Fmt.end(), LO, Target: TI); |
127 | |
128 | return H.get(); |
129 | } |
130 | |
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 | |
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 | |
178 | void StrToNumCheck::check(const MatchFinder::MatchResult &Result) { |
179 | const auto *Call = Result.Nodes.getNodeAs<CallExpr>(ID: "expr" ); |
180 | const FunctionDecl *FuncDecl = nullptr; |
181 | ConversionKind Conversion = ConversionKind::None; |
182 | |
183 | if (const auto *ConverterFunc = |
184 | Result.Nodes.getNodeAs<FunctionDecl>(ID: "converter" )) { |
185 | // Converter functions are always incorrect to use. |
186 | FuncDecl = ConverterFunc; |
187 | Conversion = classifyConversionFunc(FD: ConverterFunc); |
188 | } else if (const auto *FFD = |
189 | Result.Nodes.getNodeAs<FunctionDecl>(ID: "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(Arg: 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(Fmt: FmtStr, LO: getLangOpts(), |
215 | TI: 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(K: Conversion) |
227 | << classifyReplacement(K: Conversion); |
228 | } |
229 | |
230 | } // namespace clang::tidy::cert |
231 | |