235 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "UncheckedStringToNumberConversionCheck.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 16using namespace clang::ast_matchers;17 18namespace clang::tidy::bugprone {19 20void UncheckedStringToNumberConversionCheck::registerMatchers(21 MatchFinder *Finder) {22 // Match any function call to the C standard library string conversion23 // functions that do no error checking.24 Finder->addMatcher(25 callExpr(26 callee(functionDecl(anyOf(27 functionDecl(hasAnyName("::atoi", "::atof", "::atol", "::atoll"))28 .bind("converter"),29 functionDecl(hasAnyName("::scanf", "::sscanf", "::fscanf",30 "::vfscanf", "::vscanf", "::vsscanf"))31 .bind("formatted")))))32 .bind("expr"),33 this);34}35 36namespace {37enum class ConversionKind {38 None,39 ToInt,40 ToUInt,41 ToLongInt,42 ToLongUInt,43 ToIntMax,44 ToUIntMax,45 ToFloat,46 ToDouble,47 ToLongDouble48};49 50} // namespace51 52static ConversionKind classifyConversionFunc(const FunctionDecl *FD) {53 return llvm::StringSwitch<ConversionKind>(FD->getName())54 .Cases({"atoi", "atol"}, ConversionKind::ToInt)55 .Case("atoll", ConversionKind::ToLongInt)56 .Case("atof", ConversionKind::ToDouble)57 .Default(ConversionKind::None);58}59 60static ConversionKind classifyFormatString(StringRef Fmt, const LangOptions &LO,61 const TargetInfo &TI) {62 // Scan the format string for the first problematic format specifier, then63 // report that as the conversion type. This will miss additional conversion64 // specifiers, but that is acceptable behavior.65 66 class Handler : public analyze_format_string::FormatStringHandler {67 ConversionKind CK = ConversionKind::None;68 69 bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS,70 const char *StartSpecifier,71 unsigned SpecifierLen) override {72 // If we just consume the argument without assignment, we don't care73 // about it having conversion errors.74 if (!FS.consumesDataArgument())75 return true;76 77 // Get the conversion specifier and use it to determine the conversion78 // kind.79 const analyze_scanf::ScanfConversionSpecifier SCS =80 FS.getConversionSpecifier();81 if (SCS.isIntArg()) {82 switch (FS.getLengthModifier().getKind()) {83 case analyze_scanf::LengthModifier::AsLongLong:84 CK = ConversionKind::ToLongInt;85 break;86 case analyze_scanf::LengthModifier::AsIntMax:87 CK = ConversionKind::ToIntMax;88 break;89 default:90 CK = ConversionKind::ToInt;91 break;92 }93 } else if (SCS.isUIntArg()) {94 switch (FS.getLengthModifier().getKind()) {95 case analyze_scanf::LengthModifier::AsLongLong:96 CK = ConversionKind::ToLongUInt;97 break;98 case analyze_scanf::LengthModifier::AsIntMax:99 CK = ConversionKind::ToUIntMax;100 break;101 default:102 CK = ConversionKind::ToUInt;103 break;104 }105 } else if (SCS.isDoubleArg()) {106 switch (FS.getLengthModifier().getKind()) {107 case analyze_scanf::LengthModifier::AsLongDouble:108 CK = ConversionKind::ToLongDouble;109 break;110 case analyze_scanf::LengthModifier::AsLong:111 CK = ConversionKind::ToDouble;112 break;113 default:114 CK = ConversionKind::ToFloat;115 break;116 }117 }118 119 // Continue if we have yet to find a conversion kind that we care about.120 return CK == ConversionKind::None;121 }122 123 public:124 Handler() = default;125 126 ConversionKind get() const { return CK; }127 };128 129 Handler H;130 analyze_format_string::ParseScanfString(H, Fmt.begin(), Fmt.end(), LO, TI);131 132 return H.get();133}134 135static StringRef classifyConversionType(ConversionKind K) {136 switch (K) {137 case ConversionKind::None:138 llvm_unreachable("Unexpected conversion kind");139 case ConversionKind::ToInt:140 case ConversionKind::ToLongInt:141 case ConversionKind::ToIntMax:142 return "an integer value";143 case ConversionKind::ToUInt:144 case ConversionKind::ToLongUInt:145 case ConversionKind::ToUIntMax:146 return "an unsigned integer value";147 case ConversionKind::ToFloat:148 case ConversionKind::ToDouble:149 case ConversionKind::ToLongDouble:150 return "a floating-point value";151 }152 llvm_unreachable("Unknown conversion kind");153}154 155static StringRef classifyReplacement(ConversionKind K) {156 switch (K) {157 case ConversionKind::None:158 llvm_unreachable("Unexpected conversion kind");159 case ConversionKind::ToInt:160 return "strtol";161 case ConversionKind::ToUInt:162 return "strtoul";163 case ConversionKind::ToIntMax:164 return "strtoimax";165 case ConversionKind::ToLongInt:166 return "strtoll";167 case ConversionKind::ToLongUInt:168 return "strtoull";169 case ConversionKind::ToUIntMax:170 return "strtoumax";171 case ConversionKind::ToFloat:172 return "strtof";173 case ConversionKind::ToDouble:174 return "strtod";175 case ConversionKind::ToLongDouble:176 return "strtold";177 }178 llvm_unreachable("Unknown conversion kind");179}180 181void UncheckedStringToNumberConversionCheck::check(182 const MatchFinder::MatchResult &Result) {183 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("expr");184 const FunctionDecl *FuncDecl = nullptr;185 ConversionKind Conversion = ConversionKind::None;186 187 if (const auto *ConverterFunc =188 Result.Nodes.getNodeAs<FunctionDecl>("converter")) {189 // Converter functions are always incorrect to use.190 FuncDecl = ConverterFunc;191 Conversion = classifyConversionFunc(ConverterFunc);192 } else if (const auto *FFD =193 Result.Nodes.getNodeAs<FunctionDecl>("formatted")) {194 StringRef FmtStr;195 // The format string comes from the call expression and depends on which196 // flavor of scanf is called.197 // Index 0: scanf, vscanf, Index 1: fscanf, sscanf, vfscanf, vsscanf.198 const unsigned Idx =199 (FFD->getName() == "scanf" || FFD->getName() == "vscanf") ? 0 : 1;200 201 // Given the index, see if the call expression argument at that index is202 // a string literal.203 if (Call->getNumArgs() < Idx)204 return;205 206 if (const Expr *Arg = Call->getArg(Idx)->IgnoreParenImpCasts()) {207 if (const auto *SL = dyn_cast<StringLiteral>(Arg)) {208 FmtStr = SL->getString();209 }210 }211 212 // If we could not get the format string, bail out.213 if (FmtStr.empty())214 return;215 216 // Formatted input functions need further checking of the format string to217 // determine whether a problematic conversion may be happening.218 Conversion = classifyFormatString(FmtStr, getLangOpts(),219 Result.Context->getTargetInfo());220 if (Conversion != ConversionKind::None)221 FuncDecl = FFD;222 }223 224 if (!FuncDecl)225 return;226 227 diag(Call->getExprLoc(),228 "%0 used to convert a string to %1, but function will not report "229 "conversion errors; consider using '%2' instead")230 << FuncDecl << classifyConversionType(Conversion)231 << classifyReplacement(Conversion);232}233 234} // namespace clang::tidy::bugprone235