clang-tools  14.0.0git
StrToNumCheck.cpp
Go to the documentation of this file.
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 {
19 namespace tidy {
20 namespace cert {
21 
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 
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 
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 
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 
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 
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
clang::ast_matchers
Definition: AbseilMatcher.h:14
StrToNumCheck.h
clang::clangd::check
bool check(llvm::StringRef File, llvm::function_ref< bool(const Position &)> ShouldCheckLine, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:258
clang::tidy::bugprone::model::MixFlags::None
@ None
Mix between the two parameters is not possible.
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
K
Kind K
Definition: Rename.cpp:436