| //===-- StrToNumCheck.cpp - clang-tidy ------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "StrToNumCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/FormatString.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include <cassert> |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace cert { |
| |
| void StrToNumCheck::registerMatchers(MatchFinder *Finder) { |
| // Match any function call to the C standard library string conversion |
| // functions that do no error checking. |
| Finder->addMatcher( |
| callExpr( |
| callee(functionDecl(anyOf( |
| functionDecl(hasAnyName("::atoi", "::atof", "::atol", "::atoll")) |
| .bind("converter"), |
| functionDecl(hasAnyName("::scanf", "::sscanf", "::fscanf", |
| "::vfscanf", "::vscanf", "::vsscanf")) |
| .bind("formatted"))))) |
| .bind("expr"), |
| this); |
| } |
| |
| namespace { |
| enum class ConversionKind { |
| None, |
| ToInt, |
| ToUInt, |
| ToLongInt, |
| ToLongUInt, |
| ToIntMax, |
| ToUIntMax, |
| ToFloat, |
| ToDouble, |
| ToLongDouble |
| }; |
| |
| ConversionKind classifyConversionFunc(const FunctionDecl *FD) { |
| return llvm::StringSwitch<ConversionKind>(FD->getName()) |
| .Cases("atoi", "atol", ConversionKind::ToInt) |
| .Case("atoll", ConversionKind::ToLongInt) |
| .Case("atof", ConversionKind::ToDouble) |
| .Default(ConversionKind::None); |
| } |
| |
| ConversionKind classifyFormatString(StringRef Fmt, const LangOptions &LO, |
| const TargetInfo &TI) { |
| // Scan the format string for the first problematic format specifier, then |
| // report that as the conversion type. This will miss additional conversion |
| // specifiers, but that is acceptable behavior. |
| |
| class Handler : public analyze_format_string::FormatStringHandler { |
| ConversionKind CK; |
| |
| bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS, |
| const char *StartSpecifier, |
| unsigned SpecifierLen) override { |
| // If we just consume the argument without assignment, we don't care |
| // about it having conversion errors. |
| if (!FS.consumesDataArgument()) |
| return true; |
| |
| // Get the conversion specifier and use it to determine the conversion |
| // kind. |
| analyze_scanf::ScanfConversionSpecifier SCS = FS.getConversionSpecifier(); |
| if (SCS.isIntArg()) { |
| switch (FS.getLengthModifier().getKind()) { |
| case analyze_scanf::LengthModifier::AsLongLong: |
| CK = ConversionKind::ToLongInt; |
| break; |
| case analyze_scanf::LengthModifier::AsIntMax: |
| CK = ConversionKind::ToIntMax; |
| break; |
| default: |
| CK = ConversionKind::ToInt; |
| break; |
| } |
| } else if (SCS.isUIntArg()) { |
| switch (FS.getLengthModifier().getKind()) { |
| case analyze_scanf::LengthModifier::AsLongLong: |
| CK = ConversionKind::ToLongUInt; |
| break; |
| case analyze_scanf::LengthModifier::AsIntMax: |
| CK = ConversionKind::ToUIntMax; |
| break; |
| default: |
| CK = ConversionKind::ToUInt; |
| break; |
| } |
| } else if (SCS.isDoubleArg()) { |
| switch (FS.getLengthModifier().getKind()) { |
| case analyze_scanf::LengthModifier::AsLongDouble: |
| CK = ConversionKind::ToLongDouble; |
| break; |
| case analyze_scanf::LengthModifier::AsLong: |
| CK = ConversionKind::ToDouble; |
| break; |
| default: |
| CK = ConversionKind::ToFloat; |
| break; |
| } |
| } |
| |
| // Continue if we have yet to find a conversion kind that we care about. |
| return CK == ConversionKind::None; |
| } |
| |
| public: |
| Handler() : CK(ConversionKind::None) {} |
| |
| ConversionKind get() const { return CK; } |
| }; |
| |
| Handler H; |
| analyze_format_string::ParseScanfString(H, Fmt.begin(), Fmt.end(), LO, TI); |
| |
| return H.get(); |
| } |
| |
| StringRef classifyConversionType(ConversionKind K) { |
| switch (K) { |
| case ConversionKind::None: |
| llvm_unreachable("Unexpected conversion kind"); |
| case ConversionKind::ToInt: |
| case ConversionKind::ToLongInt: |
| case ConversionKind::ToIntMax: |
| return "an integer value"; |
| case ConversionKind::ToUInt: |
| case ConversionKind::ToLongUInt: |
| case ConversionKind::ToUIntMax: |
| return "an unsigned integer value"; |
| case ConversionKind::ToFloat: |
| case ConversionKind::ToDouble: |
| case ConversionKind::ToLongDouble: |
| return "a floating-point value"; |
| } |
| llvm_unreachable("Unknown conversion kind"); |
| } |
| |
| StringRef classifyReplacement(ConversionKind K) { |
| switch (K) { |
| case ConversionKind::None: |
| llvm_unreachable("Unexpected conversion kind"); |
| case ConversionKind::ToInt: |
| return "strtol"; |
| case ConversionKind::ToUInt: |
| return "strtoul"; |
| case ConversionKind::ToIntMax: |
| return "strtoimax"; |
| case ConversionKind::ToLongInt: |
| return "strtoll"; |
| case ConversionKind::ToLongUInt: |
| return "strtoull"; |
| case ConversionKind::ToUIntMax: |
| return "strtoumax"; |
| case ConversionKind::ToFloat: |
| return "strtof"; |
| case ConversionKind::ToDouble: |
| return "strtod"; |
| case ConversionKind::ToLongDouble: |
| return "strtold"; |
| } |
| llvm_unreachable("Unknown conversion kind"); |
| } |
| } // unnamed namespace |
| |
| void StrToNumCheck::check(const MatchFinder::MatchResult &Result) { |
| const auto *Call = Result.Nodes.getNodeAs<CallExpr>("expr"); |
| const FunctionDecl *FuncDecl = nullptr; |
| ConversionKind Conversion; |
| |
| if (const auto *ConverterFunc = |
| Result.Nodes.getNodeAs<FunctionDecl>("converter")) { |
| // Converter functions are always incorrect to use. |
| FuncDecl = ConverterFunc; |
| Conversion = classifyConversionFunc(ConverterFunc); |
| } else if (const auto *FFD = |
| Result.Nodes.getNodeAs<FunctionDecl>("formatted")) { |
| StringRef FmtStr; |
| // The format string comes from the call expression and depends on which |
| // flavor of scanf is called. |
| // Index 0: scanf, vscanf, Index 1: fscanf, sscanf, vfscanf, vsscanf. |
| unsigned Idx = |
| (FFD->getName() == "scanf" || FFD->getName() == "vscanf") ? 0 : 1; |
| |
| // Given the index, see if the call expression argument at that index is |
| // a string literal. |
| if (Call->getNumArgs() < Idx) |
| return; |
| |
| if (const Expr *Arg = Call->getArg(Idx)->IgnoreParenImpCasts()) { |
| if (const auto *SL = dyn_cast<StringLiteral>(Arg)) { |
| FmtStr = SL->getString(); |
| } |
| } |
| |
| // If we could not get the format string, bail out. |
| if (FmtStr.empty()) |
| return; |
| |
| // Formatted input functions need further checking of the format string to |
| // determine whether a problematic conversion may be happening. |
| Conversion = classifyFormatString(FmtStr, getLangOpts(), |
| Result.Context->getTargetInfo()); |
| if (Conversion != ConversionKind::None) |
| FuncDecl = FFD; |
| } |
| |
| if (!FuncDecl) |
| return; |
| |
| diag(Call->getExprLoc(), |
| "%0 used to convert a string to %1, but function will not report " |
| "conversion errors; consider using '%2' instead") |
| << FuncDecl << classifyConversionType(Conversion) |
| << classifyReplacement(Conversion); |
| } |
| |
| } // namespace cert |
| } // namespace tidy |
| } // namespace clang |