1 //===-- StrToNumCheck.cpp - clang-tidy ------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
16 using namespace clang::ast_matchers
;
18 namespace clang::tidy::cert
{
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.
25 callee(functionDecl(anyOf(
26 functionDecl(hasAnyName("::atoi", "::atof", "::atol", "::atoll"))
28 functionDecl(hasAnyName("::scanf", "::sscanf", "::fscanf",
29 "::vfscanf", "::vscanf", "::vsscanf"))
30 .bind("formatted")))))
36 enum class ConversionKind
{
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
);
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.
63 class Handler
: public analyze_format_string::FormatStringHandler
{
64 ConversionKind CK
= ConversionKind::None
;
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())
74 // Get the conversion specifier and use it to determine the conversion
76 analyze_scanf::ScanfConversionSpecifier SCS
= FS
.getConversionSpecifier();
78 switch (FS
.getLengthModifier().getKind()) {
79 case analyze_scanf::LengthModifier::AsLongLong
:
80 CK
= ConversionKind::ToLongInt
;
82 case analyze_scanf::LengthModifier::AsIntMax
:
83 CK
= ConversionKind::ToIntMax
;
86 CK
= ConversionKind::ToInt
;
89 } else if (SCS
.isUIntArg()) {
90 switch (FS
.getLengthModifier().getKind()) {
91 case analyze_scanf::LengthModifier::AsLongLong
:
92 CK
= ConversionKind::ToLongUInt
;
94 case analyze_scanf::LengthModifier::AsIntMax
:
95 CK
= ConversionKind::ToUIntMax
;
98 CK
= ConversionKind::ToUInt
;
101 } else if (SCS
.isDoubleArg()) {
102 switch (FS
.getLengthModifier().getKind()) {
103 case analyze_scanf::LengthModifier::AsLongDouble
:
104 CK
= ConversionKind::ToLongDouble
;
106 case analyze_scanf::LengthModifier::AsLong
:
107 CK
= ConversionKind::ToDouble
;
110 CK
= ConversionKind::ToFloat
;
115 // Continue if we have yet to find a conversion kind that we care about.
116 return CK
== ConversionKind::None
;
122 ConversionKind
get() const { return CK
; }
126 analyze_format_string::ParseScanfString(H
, Fmt
.begin(), Fmt
.end(), LO
, TI
);
131 StringRef
classifyConversionType(ConversionKind 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";
148 llvm_unreachable("Unknown conversion kind");
151 StringRef
classifyReplacement(ConversionKind K
) {
153 case ConversionKind::None
:
154 llvm_unreachable("Unexpected conversion kind");
155 case ConversionKind::ToInt
:
157 case ConversionKind::ToUInt
:
159 case ConversionKind::ToIntMax
:
161 case ConversionKind::ToLongInt
:
163 case ConversionKind::ToLongUInt
:
165 case ConversionKind::ToUIntMax
:
167 case ConversionKind::ToFloat
:
169 case ConversionKind::ToDouble
:
171 case ConversionKind::ToLongDouble
:
174 llvm_unreachable("Unknown conversion kind");
176 } // unnamed namespace
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
;
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")) {
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.
195 (FFD
->getName() == "scanf" || FFD
->getName() == "vscanf") ? 0 : 1;
197 // Given the index, see if the call expression argument at that index is
199 if (Call
->getNumArgs() < Idx
)
202 if (const Expr
*Arg
= Call
->getArg(Idx
)->IgnoreParenImpCasts()) {
203 if (const auto *SL
= dyn_cast
<StringLiteral
>(Arg
)) {
204 FmtStr
= SL
->getString();
208 // If we could not get the format string, bail out.
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
)
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
);
230 } // namespace clang::tidy::cert