diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h index b53c042a1ca15..f253c14cc4870 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_CHECKERHELPERS_H #include "clang/AST/Stmt.h" +#include "llvm/ADT/Optional.h" #include namespace clang { @@ -22,6 +23,7 @@ class Expr; class VarDecl; class QualType; class AttributedType; +class Preprocessor; namespace ento { @@ -62,8 +64,13 @@ enum class Nullability : char { /// Get nullability annotation for a given type. Nullability getNullabilityAnnotation(QualType Type); -} // end GR namespace +/// Try to parse the value of a defined preprocessor macro. We can only parse +/// simple expressions that consist of an optional minus sign token and then a +/// token for an integer. If we cannot parse the value then None is returned. +llvm::Optional tryExpandAsInteger(StringRef Macro, const Preprocessor &PP); -} // end clang namespace +} // namespace ento + +} // namespace clang #endif diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index e8668775ab855..608015781c49f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -55,6 +55,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" using namespace clang; using namespace clang::ento; @@ -241,7 +242,7 @@ class StdLibraryFunctionsChecker : public Checker { const CallExpr *CE, CheckerContext &C) const; - void initFunctionSummaries(BasicValueFactory &BVF) const; + void initFunctionSummaries(CheckerContext &C) const; }; } // end of anonymous namespace @@ -286,6 +287,12 @@ ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange( // "WithinRange R" is treated as "outside [T_MIN, T_MAX] \ R". // We cut off [T_MIN, min(R) - 1] and [max(R) + 1, T_MAX] if necessary, // and then cut away all holes in R one by one. + // + // E.g. consider a range list R as [A, B] and [C, D] + // -------+--------+------------------+------------+-----------> + // A B C D + // Then we assume that the value is not in [-inf, A - 1], + // then not in [D + 1, +inf], then not in [B + 1, C - 1] if (auto N = V.getAs()) { const IntRangeVector &R = getRanges(); size_t E = R.size(); @@ -312,10 +319,11 @@ ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange( for (size_t I = 1; I != E; ++I) { const llvm::APSInt &Min = BVF.getValue(R[I - 1].second + 1ULL, T); const llvm::APSInt &Max = BVF.getValue(R[I].first - 1ULL, T); - assert(Min <= Max); - State = CM.assumeInclusiveRange(State, *N, Min, Max, false); - if (!State) - return nullptr; + if (Min <= Max) { + State = CM.assumeInclusiveRange(State, *N, Min, Max, false); + if (!State) + return nullptr; + } } } @@ -449,9 +457,7 @@ StdLibraryFunctionsChecker::findFunctionSummary(const FunctionDecl *FD, if (!FD) return None; - SValBuilder &SVB = C.getSValBuilder(); - BasicValueFactory &BVF = SVB.getBasicValueFactory(); - initFunctionSummaries(BVF); + initFunctionSummaries(C); IdentifierInfo *II = FD->getIdentifier(); if (!II) @@ -478,11 +484,13 @@ StdLibraryFunctionsChecker::findFunctionSummary(const FunctionDecl *FD, } void StdLibraryFunctionsChecker::initFunctionSummaries( - BasicValueFactory &BVF) const { + CheckerContext &C) const { if (!FunctionSummaryMap.empty()) return; - ASTContext &ACtx = BVF.getContext(); + SValBuilder &SVB = C.getSValBuilder(); + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + const ASTContext &ACtx = BVF.getContext(); // These types are useful for writing specifications quickly, // New specifications should probably introduce more types. @@ -491,15 +499,28 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // of function summary for common cases (eg. ssize_t could be int or long // or long long, so three summary variants would be enough). // Of course, function variants are also useful for C++ overloads. - QualType Irrelevant; // A placeholder, whenever we do not care about the type. - QualType IntTy = ACtx.IntTy; - QualType LongTy = ACtx.LongTy; - QualType LongLongTy = ACtx.LongLongTy; - QualType SizeTy = ACtx.getSizeType(); - - RangeInt IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); - RangeInt LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); - RangeInt LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue(); + const QualType + Irrelevant; // A placeholder, whenever we do not care about the type. + const QualType IntTy = ACtx.IntTy; + const QualType LongTy = ACtx.LongTy; + const QualType LongLongTy = ACtx.LongLongTy; + const QualType SizeTy = ACtx.getSizeType(); + + const RangeInt IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); + const RangeInt LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); + const RangeInt LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue(); + + const RangeInt UCharMax = + BVF.getMaxValue(ACtx.UnsignedCharTy).getLimitedValue(); + + // The platform dependent value of EOF. + // Try our best to parse this from the Preprocessor, otherwise fallback to -1. + const auto EOFv = [&C]() -> RangeInt { + if (const llvm::Optional OptInt = + tryExpandAsInteger("EOF", C.getPreprocessor())) + return *OptInt; + return -1; + }(); // We are finally ready to define specifications for all supported functions. // @@ -552,7 +573,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // Templates for summaries that are reused by many functions. auto Getc = [&]() { return Summary(ArgTypes{Irrelevant}, RetType{IntTy}, NoEvalCall) - .Case({ReturnValueCondition(WithinRange, Range(-1, 255))}); + .Case( + {ReturnValueCondition(WithinRange, {{EOFv, EOFv}, {0, UCharMax}})}); }; auto Read = [&](RetType R, RangeInt Max) { return Summary(ArgTypes{Irrelevant, Irrelevant, SizeTy}, RetType{R}, @@ -587,10 +609,12 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // The locale-specific range. // No post-condition. We are completely unaware of // locale-specific return values. - .Case({ArgumentCondition(0U, WithinRange, {{128, 255}})}) - .Case({ArgumentCondition( - 0U, OutOfRange, - {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {128, 255}}), + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharMax}})}) + .Case({ArgumentCondition(0U, OutOfRange, + {{'0', '9'}, + {'A', 'Z'}, + {'a', 'z'}, + {128, UCharMax}}), ReturnValueCondition(WithinRange, SingleValue(0))})}, }, { @@ -601,11 +625,11 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( {{'A', 'Z'}, {'a', 'z'}}), ReturnValueCondition(OutOfRange, SingleValue(0))}) // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, 255}})}) - .Case( - {ArgumentCondition(0U, OutOfRange, - {{'A', 'Z'}, {'a', 'z'}, {128, 255}}), - ReturnValueCondition(WithinRange, SingleValue(0))})}, + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharMax}})}) + .Case({ArgumentCondition( + 0U, OutOfRange, + {{'A', 'Z'}, {'a', 'z'}, {128, UCharMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))})}, }, { "isascii", @@ -668,9 +692,9 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( ArgumentCondition(0U, OutOfRange, Range('a', 'z')), ReturnValueCondition(WithinRange, SingleValue(0))}) // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, 255}})}) + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharMax}})}) // Is not an unsigned char. - .Case({ArgumentCondition(0U, OutOfRange, Range(0, 255)), + .Case({ArgumentCondition(0U, OutOfRange, Range(0, UCharMax)), ReturnValueCondition(WithinRange, SingleValue(0))})}, }, { @@ -704,9 +728,10 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( {{9, 13}, {' ', ' '}}), ReturnValueCondition(OutOfRange, SingleValue(0))}) // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, 255}})}) - .Case({ArgumentCondition(0U, OutOfRange, - {{9, 13}, {' ', ' '}, {128, 255}}), + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharMax}})}) + .Case({ArgumentCondition( + 0U, OutOfRange, + {{9, 13}, {' ', ' '}, {128, UCharMax}}), ReturnValueCondition(WithinRange, SingleValue(0))})}, }, { @@ -717,10 +742,10 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .Case({ArgumentCondition(0U, WithinRange, Range('A', 'Z')), ReturnValueCondition(OutOfRange, SingleValue(0))}) // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, 255}})}) + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharMax}})}) // Other. .Case({ArgumentCondition(0U, OutOfRange, - {{'A', 'Z'}, {128, 255}}), + {{'A', 'Z'}, {128, UCharMax}}), ReturnValueCondition(WithinRange, SingleValue(0))})}, }, { @@ -740,9 +765,10 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // The getc() family of functions that returns either a char or an EOF. {"getc", Summaries{Getc()}}, {"fgetc", Summaries{Getc()}}, - {"getchar", Summaries{Summary(ArgTypes{}, RetType{IntTy}, NoEvalCall) - .Case({ReturnValueCondition(WithinRange, - Range(-1, 255))})}}, + {"getchar", + Summaries{Summary(ArgTypes{}, RetType{IntTy}, NoEvalCall) + .Case({ReturnValueCondition( + WithinRange, {{EOFv, EOFv}, {0, UCharMax}})})}}, // read()-like functions that never return more than buffer size. // We are not sure how ssize_t is defined on every platform, so we diff --git a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index 11693132de687..e43b172d018d4 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -13,6 +13,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" +#include "clang/Lex/Preprocessor.h" namespace clang { @@ -109,6 +110,45 @@ Nullability getNullabilityAnnotation(QualType Type) { return Nullability::Unspecified; } +llvm::Optional tryExpandAsInteger(StringRef Macro, + const Preprocessor &PP) { + const auto *MacroII = PP.getIdentifierInfo(Macro); + if (!MacroII) + return llvm::None; + const MacroInfo *MI = PP.getMacroInfo(MacroII); + if (!MI) + return llvm::None; + + // Filter out parens. + std::vector FilteredTokens; + FilteredTokens.reserve(MI->tokens().size()); + for (auto &T : MI->tokens()) + if (!T.isOneOf(tok::l_paren, tok::r_paren)) + FilteredTokens.push_back(T); + + if (FilteredTokens.size() > 2) + return llvm::None; + + // Parse an integer at the end of the macro definition. + const Token &T = FilteredTokens.back(); + if (!T.isLiteral()) + return llvm::None; + StringRef ValueStr = StringRef(T.getLiteralData(), T.getLength()); + llvm::APInt IntValue; + constexpr unsigned AutoSenseRadix = 0; + if (ValueStr.getAsInteger(AutoSenseRadix, IntValue)) + return llvm::None; + + // Parse an optional minus sign. + if (FilteredTokens.size() == 2) { + if (FilteredTokens.front().is(tok::minus)) + IntValue = -IntValue; + else + return llvm::None; + } + + return IntValue.getSExtValue(); +} -} // end namespace ento -} // end namespace clang +} // namespace ento +} // namespace clang diff --git a/clang/test/Analysis/std-c-library-functions-eof.c b/clang/test/Analysis/std-c-library-functions-eof.c new file mode 100644 index 0000000000000..0b09a1c63d97b --- /dev/null +++ b/clang/test/Analysis/std-c-library-functions-eof.c @@ -0,0 +1,26 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,apiModeling.StdCLibraryFunctions,debug.ExprInspection -verify -analyzer-config eagerly-assume=false %s +// RUN: %clang_analyze_cc1 -triple i686-unknown-linux -analyzer-checker=core,apiModeling.StdCLibraryFunctions,debug.ExprInspection -verify -analyzer-config eagerly-assume=false %s +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux -analyzer-checker=core,apiModeling.StdCLibraryFunctions,debug.ExprInspection -verify -analyzer-config eagerly-assume=false %s +// RUN: %clang_analyze_cc1 -triple armv7-a15-linux -analyzer-checker=core,apiModeling.StdCLibraryFunctions,debug.ExprInspection -verify -analyzer-config eagerly-assume=false %s +// RUN: %clang_analyze_cc1 -triple thumbv7-a15-linux -analyzer-checker=core,apiModeling.StdCLibraryFunctions,debug.ExprInspection -verify -analyzer-config eagerly-assume=false %s + +void clang_analyzer_eval(int); + +typedef struct FILE FILE; +// Unorthodox EOF value. +#define EOF (-2) + +int getc(FILE *); +void test_getc(FILE *fp) { + + int x; + while ((x = getc(fp)) != EOF) { + clang_analyzer_eval(x > 255); // expected-warning{{FALSE}} + clang_analyzer_eval(x >= 0); // expected-warning{{TRUE}} + } + + int y = getc(fp); + if (y < 0) { + clang_analyzer_eval(y == -2); // expected-warning{{TRUE}} + } +}