forked from OSchip/llvm-project
235 lines
7.4 KiB
C++
235 lines
7.4 KiB
C++
//===-- 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
|